2015-10-19 55 views
2

我有這樣的程序:直接分配,以老一代

public static void main(String[] args) { 
     int sum = 0; 
     LinkedList<Integer> ll = new LinkedList<>(); 
     for(int i=0;i<Long.MAX_VALUE;i++) { 
      sum += i; 
      if (sum % 3 == 0){ 
       ll.add(i); 
       if (ll.size() % 1000 == 0) 
        System.out.println("Linked List size: " + ll.size()); 
      } 
     } 
    }` 

我希望看到什麼是年輕一代正在創建整數對象,他們中的一些添加到鏈表移動到老一代。所以我預計年輕一代GC會隨着物體移動到倖存空間,然後從那裏傳到老一代而持續發生。但是我發現,這是一直在發生的老一代GC,並且根本沒有發生年輕一代GC。這是JVM正在做的某種優化嗎?老一代直接創造物體的地方在哪裏?正如你在下面的圖片中看到的,年輕的gc只發生過兩次,而老gc發生了41次。 Old Generation GC only

接下來,我嘗試了相同的代碼,只是不是將整數對象添加到鏈表中,而是創建了一個新的Object(),令我驚訝的是沒有年輕人或老年人gc。

public static void main(String[] args) { 
    int sum = 0; 
    LinkedList<Integer> ll = new LinkedList<>(); 
    for(int i=0;i<Long.MAX_VALUE;i++) { 
     sum += i; 
     Object obj = new Object(); 
    } 
} 

No Young or Old GC

然後,我創建的隨機字符串對象:

public static void main(String[] args) { 
    int sum = 0; 
    LinkedList<Integer> ll = new LinkedList<>(); 
    for(int i=0;i<Long.MAX_VALUE;i++) { 
     sum += i; 
     String s = String.valueOf(Math.random()); 
    } 
} 

現在我看到的物體famililar拉鋸格局被轉移到生存空間:

5.833:[GC(分配失敗)[PSYoungGen:1048576K-> 1984K(1223168K)] 1048576K-> 2000K(4019712K),0.0035789s [GC(Allocation Failure)[PSYoungGen:1050560K-> 2000K(1223168K)] 1050576K-> 2024K(4019712K),0.0023286secs] [ecs] [Times:user = 0.01sys = 0.00,real = 0.00sec] 12.678 [GC(分配失敗)[PSYoungGen:1050576K-> 1968K(1223168K)] 1050600K-> 2000K(4019712K),0.0016530秒] [時間:用戶= 0.01 sys = 0.00,實際= 0.00秒] [ [GC(分配失敗)[PSYoungGen:1050544K-> 2000K(1223168K)] 1050576K-> 2040K(4019712K),0.0016131秒] [時間:用戶= 0.01秒] = 0.00,實際= 0.00秒] 24.346 [GC(分配失敗)[PSYoungGen:1050576K-> 1952K(1223168K)] 1050616K-> 2000K(4019712K),0.0018461秒] [Times:user = 0.01 sys = 0.00] ,實際= 0.00秒] 38.519:[GC(分配失敗)[PSYoungGen:1050528K-> 1984K(1395712K)] 1050576K-> 2040K(4192256K),0.0022490secs] [Times:user = 0.01 sys = 0.00,real = 0.00sec] 47.998: [GC(分配失敗)[PSYoungGen:1395648K-> 256K(1394176K)] 1395704K-> 2153K(4190720K),0.0024607秒] [時間:用戶= 0.01 SYS = 0.00,真= 0.00秒]

所以我的問題是GC足夠聰明,可以看到創建的對象在任何地方都沒有被使用並丟棄它們?爲什麼不用字符串?

回答

3

在您的第一個示例中,您將不斷向LinkedList添加對象,這是應用程序代碼中最老的對象。只要這個對象被提升到老一代,向該列表添加對象意味着修改舊一代的成員。這意味着下一個垃圾收集必須檢查新創建的對象是否可以被舊對象訪問 - 這對每個第三個對象來說都是合理的。

在你的第二個例子中,你只是創建一個Object沒有任何副作用。這種分配可以通過熱點優化器來消除。之後,根本沒有垃圾。實際上,整個循環可以被消除,因爲增加可以被單個乘法代替。但是否這種情況與垃圾收集器的活動無關(或缺乏)。

你的第三個例子調用Math.random()哪個一個全局可見的,不可移動的效果。無論您是否使用返回的號碼,它都會推進內部使用的共享全局隨機數生成器的狀態。我猜想,僞隨機數發生器的算術過於複雜,無法將循環轉換爲單個計算步驟。

原則上,未使用的String實例的創建仍然可以被消除,但似乎與不可移動代碼交錯會觸發優化器的限制。由於隨機數生成代碼的複雜性,也可能會出現這種情況,臨時字符串的創建在這裏被認爲與性能無關。

所以你看,第二個和第三個例子與垃圾回收器沒什麼關係,而是與熱點優化器有關。此外,關於你的第一個例子,你必須考慮垃圾收集器如何工作。儘管它的名字,它沒有處理垃圾,但活着的對象找出,哪些對象被活着的對象引用,因此,自己活着。因此,這並不重要,在哪個世代空間中創建對象,但是哪個對象可能會到達它們。如果自上次收集後沒有修改舊對象,則可以執行本地收集,因爲未修改的舊對象無法到達雛鳥。但是如果一箇舊的對象被修改了,它的引用必須被遍歷來找出它是否引用了新的對象。

+0

所以我的問題是爲什麼Integer對象不是在我的第一個例子中在年輕一代中創建的。我期望在年輕一代中創建整數,然後將對該變量的引用添加到列表中,並且年輕一代的掃描將這些對象移動到舊的狀態。 –

+0

您正在得出錯誤的結論。如上所述,舊的gen *集合*並不意味着在舊空間中創建了這些對象。這隻意味着舊一代已經在gc期間被處理,或者是因爲它已被修改,或者因爲整個當前分配的內存已滿。實際上,你的Eden空間包含一個巨大的** 1GB **對象,因此聲稱該對象是在舊世代空間中創建的真的很奇怪。 – Holger

+0

看來你的'-Xms'設置非常高。我建議降低(或刪除)該設置,並在程序開始處插入延遲,然後觀察程序在*整個內存耗盡之前的行爲*。 – Holger