2011-12-08 56 views
7

這是一個我們難以理解的問題。用文字描述它很困難,但我希望能夠理解這個要點。java中保留的字符串堆大小

我知道一個字符串的實際內容被包含在一個內部char數組中。在正常情況下,字符串的保留堆大小將包含40個字節加上字符數組的大小。這是解釋here。當調用子字符串時,字符數組保留對原始字符串的引用,因此字符數組的保留大小可能比字符串本身大得多。

但是使用Yourkit或MAT一些奇怪的剖析內存使用情況時似乎發生。引用char數組保留大小的字符串不包含字符數組的保留大小。

一個示例可以是如下的(半僞代碼):

String date = "2011-11-33"; (24 bytes) 
date.value = char{1172}; (2360 bytes) 

字符串的保留大小被定義爲24個字節,而不包括字符陣列的保留尺寸。如果由於許多子字符串操作導致字符數組引用很多,這可能是有意義的。

現在,當此字符串被包括在某些類型的集合,諸如數組或列表然後此陣列的保留尺寸將包括所有的字符串包含字符陣列的保留尺寸的保留尺寸。

然後我們有一個情況是這樣的:

Array's retained size = 300 bytes 
array[0] = String 40 bytes; 
array[1] = String 40 bytes; 
array[1].value = char[] (220 bytes) 

你因此不得不考慮每個數組項,試圖找出其中保留的大小來的。

再次,這可以解釋在該陣列擁有所有持有到相同的字符數組的引用,並且因此完全陣列的保留大小是正確的琴絃。

現在我們來解決這個問題。

我保持在一個單獨的目的是,我上面討論的陣列以及一個不同的陣列使用相同的字符串的引用。在這兩個數組中,字符串都指向相同的字符數組。這是預料之中 - 畢竟我們在談論同一個字符串。然而,這個字符數組的保留大小在這個新對象中被計數。換句話說,保留的大小似乎是雙倍的。如果我刪除第一個數組,那麼第二個數組仍將保存對字符數組的引用,反之亦然。這導致了一個混亂,因爲它看起來像java持有兩個單獨的引用到相同的字符數組。怎麼會這樣?這是Java內存的問題,還是隻是分析器顯示信息的方式?

這個問題在試圖追查在我們的應用程序巨大的內存使用造成了很大的麻煩我們。

再次 - 我希望有人在那裏就能明白的問題,並解釋。

感謝您的幫助

回答

4

我在一個單獨的對象中保存了上面討論的數組的引用以及具有相同字符串的不同數組。在這兩個數組中,字符串都指向相同的字符數組。這是預料之中 - 畢竟我們在談論同一個字符串。然而,這個字符數組的保留大小在這個新對象中被計數。換句話說,保留的大小似乎是雙倍的。

你這裏是什麼在支配樹一個傳遞參考:

enter image description here

在任一數組的大小保留的字符數組應該不會出現。如果分析器以這種方式顯示,那就是誤導。

這是JProfiler如何示出了在最大對象這種情況查看:

enter image description here

包含在兩個陣列中的字符串實例,示出了陣列的實例以外,具有[及物參考]標籤。如果你想探索的實際路徑,你可以添加陣列架和字符串的圖形,並找到它們之間的所有路徑:

enter image description here

免責聲明:我公司開發的JProfiler。

+0

我將下載jprofiler的評估,看看它是否更有意義。儘管感謝您的回答。它看起來更有意義...... – slbruce

+0

不幸的是我發現jprofiler非常難以使用。我沒有時間學習如何充分發揮它的潛力,所以我只會說出你的看法:)感謝您的幫助 – slbruce

+0

爲了表示您的讚賞,您可以接受我的回答:-)讓我向你保證JProfiler根本不難使用。對於上面的例子,你只需要一個堆快照,選擇包含數組的類並激活「最大對象」視圖。 –

0

除非字符串實習,他們可以equal()但不==。從char數組構造一個String對象時,構造函數將複製char數組。 (這是屏蔽char數組值中以後更改的不可變字符串的唯一方法。)

+0

我想他是在談論兩個具有完全相同的String實例的數組。 – Thilo

+0

@Thilo - 我正在接受_「在兩個數組中,字符串都指向相同的字符數組。」_很難確保沒有在字符串中入住。 –

+0

其實這確實很微不足道。 'String s2 = s1.substring(0)' 你說的對,新的String(char [])構造函數會複製char數組。但是,新的String(String)構造函數在IBM JVM上的行爲與在Sun JVM上的行爲不同。 –

3

我會說這只是分析器顯示信息的方式。它不知道這兩個陣列應該被考慮用於「重複數據刪除」。你如何將這兩個數組包裝成某種虛擬持有者對象,然後運行你的分析器?那麼,它應該能夠照顧到「重複計數」。

+0

我同意...探查器可能計數字符串內部數組兩次。 –

+0

但是我會傾向於認同這個問題,但是如果沒有必要,這個問題似乎會導致完整的gc發生 - 換句話說,即使java會這樣看待它 – slbruce

+0

所以你說Java對使用多少堆空間感到困惑和多少空閒(並計算同一個對象兩次)?這似乎不太可能... – Thilo

0

如果用-XX:-UseTLAB

public static void main(String... args) throws Exception { 
    StringBuilder text = new StringBuilder(); 
    text.append(new char[1024]); 
    long free1 = free(); 
    String str = text.toString(); 
    long free2 = free(); 
    String [] array = { str.substring(0, 100), str.substring(101, 200) }; 
    long free3 = free(); 
    if (free3 == free2) 
     System.err.println("You must use -XX:-UseTLAB"); 
    System.out.println("To create String with 1024 chars "+(free1-free2)+" bytes\nand to create an array with two sub-string was "+(free2-free3)); 
} 

private static long free() { 
    return Runtime.getRuntime().freeMemory(); 
} 

打印

To create String with 1024 chars 2096 bytes 
and to create an array with two sub-string was 88 

運行,你可以看到它,如果它們共享相同的後端店,你可能期望消耗更多的內存。

如果您看字符串類中的代碼。

public String substring(int start, int end) { 
    // checks. 
    return ((beginIndex == 0) && (endIndex == count)) ? this : 
     new String(offset + beginIndex, endIndex - beginIndex, value); 
} 

String(int offset, int count, char value[]) { 
    this.value = value; 
    this.offset = offset; 
    this.count = count; 
} 

你可以看到,對於子字符串沒有考慮潛在價值數組的一個副本。


另一個要考慮的是-XX:+UseCompressedStrings這是在默認情況下在JVM上的新版本。這鼓勵JVM儘可能使用byte []而不是char []。

字符串和數組對象的標題大小因32位JVM,32位引用的64位JVM和64位引用的64位JVM而異。

+3

我不知道你在哪裏找到子字符串實現,但在Oracle/Sun和IBM JVM中,子字符串不會複製數組。 –

+0

我的代碼中有一個錯誤!子字符串來自StringBuilder,它必須獲取副本。 –

+0

同意。這絕對不是我看到的行爲 – slbruce