寫在前面
對於Java中的String類占用多大的內存空間這個問題,是最近面試中問的比較多的一個問題。很多小伙伴的回答的都不是很正確,有說不占空間的,有說1個字節的,有說2個字節的,有說3個字節的,有說不知道的,更讓人哭笑不得的是竟然還有人說是2的31次方。那如果真是這樣的話,服務器的內存空間還放不下一個字符串呀!作為程序員的我們,可不能鬧這種笑話呀。今天,我們就一起來聊聊Java中的String到底占用多大的內存空間!
Java對象的結構
首先,我們來下Java對象在虛擬機中的結構,這里,以HotSpot虛擬機為例。
注:圖片來源http://r6d.cn/wp7q
從上面的這張圖里面可以看出,對象在內存中的結構主要包含以下幾個部分:
- Mark Word(標記字段):對象的Mark Word部分占4個字節,其內容是一系列的標記位,比如輕量級鎖的標記位,偏向鎖標記位等等。
- Klass Pointer(Class對象指針):Class對象指針的大小也是4個字節,其指向的位置是對象對應的Class對象(其對應的元數據對象)的內存地址
- 對象實際數據:這里面包括了對象的所有成員變量,其大小由各個成員變量的大小決定,比如:byte和boolean是1個字節,short和char是2個字節,int和float是4個字節,long和double是8個字節,reference是4個字節
- 對齊:最后一部分是對齊填充的字節,按8個字節填充。
換種說法就是:
- 對象頭(object header):8 個字節(保存對象的 class 信息、ID、在虛擬機中的狀態)
- Java 原始類型數據:如 int, float, char 等類型的數據
- 引用(reference):4 個字節
- 填充符(padding)
Java中的String類型
空String占用的空間
這里,我們以Java8為例進行說明。首先,我們來看看String類中的成員變量。
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
在 Java 里數組也是對象,因此數組也有對象頭。所以,一個數組所占的空間為對象頭所占的空間加上數組長度加上數組的引用,即 8 + 4 + 4= 16 字節 。
所以,我們可以得出一個空String對象所占用的內存空間,如下所示。
對象頭(8 字節)+ 引用 (4 字節 ) + char 數組(16 字節)+ 1個 int(4字節)+ 1個long(8字節)= 40 字節
所以,小伙伴們,你們的回答正確嗎?
非空String占用的空間
如果String字符串的長度大於0的話,我們也可以得出String占用內存的計算公式,如下所示。
40 + 2 * n
其中,n為字符串的長度。
這里,可能有小伙伴會問,為什么是 40 + 2 * n 呢?這是因為40是空字符串占用的內存空間,這個我們上面已經說過了,String類實際上是把數據存儲到char[]這個成員變量數組中的,而char[]數組中的一個char類型的數據占用2個字節的空間,所以,只是String中的數據就會占用 2 * n(n為字符串的長度)個字節的空間,再加上空字符串所占用的40個字節空間,最終得出一個字符串所占用的存儲空間為: 40 + 2 * n (n為字符串長度)。
因此在代碼中大量使用String對象時,應考慮內存的實際占用情況。
注:40 + 2 * n 這個公式我們可以看成是計算String對象占用多大內存空間的通用公式。
驗證結論
接下來,我們就一起來驗證下我們上面的結論。首先,創建一個UUIDUtils類用來生成32位的UUID,如下所示。
package io.mykit.binghe.string.test;
import java.util.UUID;
/**
* @author binghe
* @version 1.0.0
* @description 生成沒有-的UUID
*/
public class UUIDUtils {
public static String getUUID(){
String uuid = UUID.randomUUID().toString();
return uuid.replace("-", "");
}
}
接下來,創建一個TestString類,在main()方法中創建一個長度為4000000的數組,然后在數組中放滿UUID字符串,如下所示。
package io.mykit.binghe.string.test;
import java.util.UUID;
/**
* @author binghe
* @version 1.0.0
* @description 測試String占用的內存空間
*/
public class TestString{
public static void main(String[] args){
String[] strContainer = new String[4000000];
for(int i = 0; i < 4000000; i++){
strContainer[i] = UUIDUtils.getUUID();
System.out.println(i);
}
//防止程序退出
while(true){
}
}
}
這里,4000000個字符串,每個字符串的長度為32,所以保存字符串數據所占用的內存空間為:(40 + 32 * 2) * 4000000 = 416000000字節,約等於416MB。
我們使用Jprofiler內存分析工具進行分析:
可以看到,使用Jprofiler內存分析工具的結果為:321MB + 96632KB,約等於417MB。之所以使用Jprofiler內存分析工具得出的結果比我們計算的大些,是因為在程序實際運行的過程中,程序內部也會生成一些字符串,這些字符串也會占用內存空間!!
所以,使用Jprofiler內存分析工具得出的結果符合我們的預期。
好了,今天就到這兒吧,我是冰河,大家有啥問題可以在下方留言,也可以加我微信:sun_shine_lyz,我拉你進群,一起交流技術,一起進階,一起牛逼~~