2010-10-19 47 views
4

我運行了這些代碼,並且遇到了一些問題,這有點奇怪。如果Java字符串是不可變的,並且StringBuilder是可變的,他們爲什麼會浪費我的代碼中相同數量的內存?

使用字符串:

while(true) 
{ 
    String s = String.valueOf(System.currentTimeMillis()); 
    System.out.println(s); 
    Thread.sleep(10); 
} 

使用StringBuilder的:

StringBuilder s = null; 
    while(true) 
    { 
     s = new StringBuilder(); 
     s.append(System.currentTimeInMillis()); 
     System.out.println(s); 
     Thread.sleep(10); 
    } 

在他們被困在12540ķ浪費內存兩種情況。在Windows XP SP2上運行此測試。

他們爲什麼浪費相同數量的內存? 爲什麼不可變的字符串停止浪費內存? 題外話:如何將StringBuilder轉換爲以特定字符集編碼的字節數組?

+0

沒有100%你是問。上述兩者都不會導致任何「浪費」或泄漏的記憶。 12540k可能只是JVM使用的基本內存或還沒有被清理的瞬態內存。 – 2010-10-19 11:04:56

+2

爲什麼不可變類會浪費內存? (而且可變嗎?) – Ishtar 2010-10-19 11:08:16

+0

我讀過常見的字符串,垃圾回收器從來沒有收集過,是否是假的? – fredcrs 2010-10-19 11:12:04

回答

6

很難弄清楚你究竟在這裏問什麼,但應用程序的行爲完全如我所料。

字符串是不可變的,垃圾收集器不會將它們取出。是不是

可變和不可變對象都可以在Java中進行垃圾收集。

實際判斷一個物體是否實際上是垃圾收集的標準是可達性。簡而言之,當垃圾收集器發現應用程序不能再使用某個對象時,該對象將被刪除。

在這兩個應用程序中,每10毫秒創建一個大小大致相同的對象。在每次迭代中,正在創建一個新對象,並將其引用分配給s,替換之前的引用。這使得以前的對象無法訪問,並有資格進行垃圾回收。在某些時候,Java VM決定運行垃圾收集器。這將擺脫所有無法訪問的對象...並且應用程序將繼續。

我看過那些常見的字符串不是垃圾收集器收集的,是假的?

這體現在兩個方面錯誤:

  • 字符串由new String(...)String.substring(...)創建等都是從其他Java對象沒有什麼不同。

  • 被攔截的字符串(通過調用String.intern())被存儲在保存在PermGen堆中的字符串池中。然而,即使是PermGen堆也是垃圾回收,雖然時間更長,通常創建對象的堆也是如此。

(曾幾何時,PermGen的堆被當作垃圾回收,但被前改變了很長的時間。)

3

因爲你正在建造和投擲。事實上,你並不是真的使用StringBuilder來構建任何字符串。注意,你正在實例化一個新的StringBuilder對象。

+1

是的,但我使用currentTimeInMillis,它不應該是相同的 – fredcrs 2010-10-19 11:20:30

+0

是的正確。我在過去有不同的經歷。你知道Java 1.4天。剛剛嘗試過,看起來很好。謝謝。 – 2010-10-19 11:26:03

4
  1. 您似乎認爲一個可變類會浪費更多的內存比不可變類。我不明白爲什麼。

  2. 你的代碼是錯誤的,如果它打算在每個循環中分配更多的內存。它只是將s引用分配給一個新對象,因此前一個對象會丟失並最終被垃圾收集。

  3. 查看JVM的OS內存是對Java分配內存的非常粗略/不精確的估計。 (String和StringBuffer和char [])都是有效的,它們爲每個字符分配大約2個字節(Java使用一些UTF-16變體)加上一個小的(可忽略大字符串)開銷。

6

你是混淆了兩個非常不同的東西:

  • 字符串是不可改變的,但是這無關他們是否是垃圾收集。但是,這意味着如果您需要對String進行大量更改(例如通過一次追加一個字符來構建一個大字符串),那麼您最終會爲垃圾收集器製作大量副本和大量工作。
  • 字符串文字(即直接寫入源代碼的字符串)是實體字符串池的一部分,通常不會垃圾收集。但是,這樣做是爲了允許源代碼中同一個字符串的多個實例替換爲對同一個對象的引用,這可以節省大量空間。這是唯一可能的,因爲字符串是不可變的,所以程序的兩個部分持有對同一個字符串的引用不會互相干擾。
+0

是的,我很困惑這些。謝謝 – fredcrs 2010-10-19 11:33:41

2

正如已經解釋過的那樣,因爲你不是在改變字符串,而是隻是指向一個新的值;舊的價值必須被垃圾收集。以下是使用stringBuffer嘗試實際改變指向的值的代碼片段。

StringBuffer s = new StringBuffer(); 
    while(true) 
    { 
     s.replace(0,13,Long.toString(System.currentTimeMillis())); 
     System.out.println(s); 
     Thread.sleep(10); 
    } 

應該指出,這並不能解決問題,因爲兩件事。首先,我們必須每次使用Long.toString()創建一個新的String,其次要調用s.toString()。這將使一個新的String分享stringBuffer的值(至少在我上次檢查時就是這種情況)。當我們做s.replace它會分配一個新的數組來保存這個新的字符串,以保持字符串的不可變性。

其實,在這個簡單的情況下,最好的,你可以做(​​據我所知)是:

while(true) 
    { 
     System.out.println(Long.toString(System.currentTimeMillis())); 
     Thread.sleep(10); 
    } 
+0

你給出的最後一個例子是最正確的一個例子,因爲它沒有使用中間字符串變量做任何shenanagins。 @fredcrs除非實際需要多次使用結果,否則不要將中間結果分配給變量。如果函數調用鏈看起來太長,則應該將某些嵌套調用重構爲單獨的函數,然後調用該函數。 – AJMansfield 2013-09-07 19:38:49

2

想後將此視爲斯蒂芬℃的答覆,但由於某些原因,我可以」噸;所以這裏有一點澄清...

String.subString(...)不會創建一個新的字符串。它引用現有字符串中的一個點,並且返回子字符串值是向您的應用程序引入內存泄漏的一種可靠方法(尤其是在基於另一個字符串列表的子字符串值構建字符串列表時)。在這種情況下

最好的做法是:

return new String(s.subString(...)); 
+0

@Jeff Knecht - 你需要明白你的事實。 'String.substring()'創建一個新的'String'。當然,新的'String'與原始的'String'共享一些狀態(即後備數組),但它仍然是一個新的'String'。其次,當然不是最好的做法,那就是'new String(s.subString(...)',只有在真正存在內存泄漏的風險時才應該這樣做。在大多數情況下,不是。 – 2012-06-27 09:14:54

相關問題