對象創建與堆
這一節主要介紹對象創建時,在堆中的一些過程。
回憶下,我們之前說的,什麼時候會發生垃圾回收?
除了在一些安全點處也許會發生垃圾回收(只是也許),如果在所需記憶體不足的情況下,一定會發生垃圾回收。
分配堆空間
首先通過設置參數,把堆空間設置為 20M,其中 新生代 10M,老年代 10M。
參數設置:
-Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails
結果為:
Heap
PSYoungGen total 9216K, used 1685K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
eden space 8192K, 20% used [0x00000007bf600000,0x00000007bf7a5580,0x00000007bfe00000)
from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
to space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
ParOldGen total 10240K, used 0K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
object space 10240K, 0% used [0x00000007bec00000,0x00000007bec00000,0x00000007bf600000)
創建一個新對象
我們首先創建一個對象,這個對象佔用 2M 的空間。
package heap;
public class CreateObject {
public static void main(String[] args) {
byte[] obj1 = new byte[1024 * 1024 * 2];
}
}
最後的輸出:
Heap
PSYoungGen total 9216K, used 3733K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
eden space 8192K, 45% used [0x00000007bf600000,0x00000007bf9a5590,0x00000007bfe00000)
from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
to space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
ParOldGen total 10240K, used 0K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
object space 10240K, 0% used [0x00000007bec00000,0x00000007bec00000,0x00000007bf600000)
可以看到,新生代 被佔用了,老年代佔用為 0K,沒有被使用。
所以,new 的對象先放在 eden 區。
填滿 eden 區
在填滿 eden 區後,會發生什麼呢?因為 survivor 區實在太小了,很難看到。所以,這裡可以藉助 Visual VM,來觀察,更加直觀。
程式如下:
package heap;
public class CreateObject {
public static void main(String[] args) {
while(true){
byte[] bytes = new byte[1024 * 512];
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
重點是看右邊的記錄圖。注意,這裡我們將每次創建對象的大小設置為了 0.5M。
當 Eden 滿的時候,會調用垃圾回收器,調用垃圾回收器後,Eden 出現了低谷,Survivor 出現了一個增長。老年區也出現了一個增長。
當 Eden 滿的時候,如果 Survivor 區有足夠的空間容納存活對象,那麼可以把存活對象放入 Survivor,多的對象放入老年區。
現在,我們把對象的大小調大。設置為 2M,這樣 Survivor 就無法存放下。
可以看到,在經過一次垃圾回收的時候(可以看到GC Time 上有波峰,說明執行了一次垃圾回收),但我們注意到,Survivor 區中並沒有被佔用。說明垃圾回收過程中,直接將存活對象放到了老年代中。
再來聊聊 survivor 區
對象通常在 Eden 區里誕生,如果經過第一次 MInor GC 後仍然存活,並且能夠被 Survivor 容納的話,該對象會被移動到 Survivor 區,並且將其年齡設置為 1 歲。對象在 Survivor 區每熬過一次 Minor GC,年齡就增加 1 歲,當它年齡增大到一定程度(默認是 15 歲),就會被晉陞到老年代。
特殊情況
有些時候,如果用戶創建了大對象,如很長的字元串或者元素很多的數組的時候。這種大對象都佔用大量的記憶體,像這種大對象,有很大概率是長時間使用的,不然為什麼要創建大對象。
如果大對象朝生夕滅,我們知道在 Java 8 中,新生代默認採用的 標記-複製 演算法,那麼對於大對象而言,是非常耗時的。
所以,如果 JVM 設置了一個閾值,那麼當分配的對象大於這個閾值的時候,會直接被分配到老年代。