天天看點

位元組跳動面試官這樣問有關字元串的問題!!

Java對象的結構

首先,我們來下Java對象在虛拟機中的結構,這裡,以HotSpot虛拟機為例。

位元組跳動面試官這樣問有關字元串的問題!!

從上面的這張圖裡面可以看出,對象在記憶體中的結構主要包含以下幾個部分:

  • 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記憶體分析工具得出的結果符合我們的預期。