Java垃圾回收淺析
垃圾回收和內存分配相關,先了解運行時數據區域的劃分及各個區域的作用。
垃圾回收主要需要考慮的3個問題:
1、什么時候回收;
2、哪些對象需要回收;
3、如何回收。
運行時數據區域
程序計數器(Program Conuter Register)
程序計數器是一塊較小的內存空間,它是當前線程執行字節碼的行號指示器,字節碼解釋工作器就是通過改變這個計數器的值來選取下一條需要執行的指令。它是線程私有的內存,也是唯一一個沒有OOM異常的區域。
Java虛擬機棧區(Java Virtual Machine Stacks)
也就是通常所說的棧區,它描述的是Java方法執行的內存模型,每個方法被執行的時候都創建一個棧幀(Stack Frame),用于存儲局部變量表、操作數棧、動態鏈接、方法出口等。每個方法被調用到完成,相當于一個棧幀在虛擬機棧中從入棧到出棧的過程。此區域也是線程私有的內存,可能拋出兩種異常:
- 如果線程請求的棧深度大于虛擬機允許的深度將拋出StackOverflowError;
- 如果虛擬機棧可以動態的擴展,擴展到無法動態的申請到足夠的內存時會拋出OOM異常。
本地方法棧(Native Method Stacks)
本地方法棧與虛擬機棧發揮的作用非常相似,區別就是虛擬機棧為虛擬機執行Java方法,本地方法棧則是為虛擬機使用到的Native方法服務。
堆區(Heap)
所有對象實例和數組都在堆區上分配,堆區是GC主要管理的區域。堆區還可以細分為新生代、老年代,新生代還分為一個Eden區和兩個Survivor區。此塊內存為所有線程共享區域,當堆中沒有足夠內存完成實例分配時會拋出OOM異常。
方法區(Method Area)
方法區也是所有線程共享區,用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯后的代碼等數據。GC在這個區域 很少出現,這個區域內存回收的目標主要是對常量池的回收和類型的卸載,回收的內存比較少,所以也有稱這個區域為永久代(Permanent Generation)的。當方法區無法滿足內存分配時拋出OOM異常。運行時常量池是方法區的一部分,用于存放編譯期生成的各種字面量和符號引用。
哪些對象需要回收?
引用計數法
給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1 ;當引用失效時,計數器值就減1 ;任何時刻計數器都為0 的對象就是不可能再被使用的。引用計數算法(Reference Counting)的實現簡單,判定效率也很高,在大部分情況下它都是一個不錯的算法,但是Java 語言中沒有選用引用計數算法來管理內存,其中最主要的原因是它很難解決對象之間的相互循環引用的問題。
根搜索算法
這個算法的基本思路就是通過一系列的名為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots 沒有任何引用鏈相連(用圖論的話來說就是從GC Roots 到這個對象不可達)時,則證明此對象是不可用的。在Java 語言里,可作為GC Roots 的對象包括下面幾種:
- 虛擬機棧(棧幀中的本地變量表)中的引用的對象。
- 方法區中的類靜態屬性引用的對象。
- 方法區中的常量引用的對象。
- 本地方法棧中JNI(即一般說的Native 方法)的引用的對象。
什么時候回收?
GC經常發生的區域是堆區,堆區還可以細分為新生代、老年代,新生代還分為一個Eden區和兩個Survivor區。
A.對象優先在Eden中分配,當Eden中沒有足夠空間時,虛擬機將發生一次Minor GC,Minor GC非常頻繁,而且速度也很快;
B.Full GC,發生在老年代的GC,當老年代沒有足夠的空間時即發生Full GC,發生Full GC一般都會有一次Minor GC。
C.發生Minor GC時,虛擬機會檢測之前每次晉升到老年代的平均大小是否大于老年代的剩余空間大小,如果大于,則進行一次Full GC,如果小于,則查看是否允許擔保失敗,如果允許,那只會進行一次Minor GC,如果不允許,則改為進行一次Full GC。
如何回收?
先看看常用的垃圾回收算法。
標記-清除算法。
缺點:標記、清除的效率都很低;標記清除后導致不連續空間。
復制算法。
優點:存活率低時高效,回收后空間連續。
缺點:內存分成大小相同的兩塊,資源浪費。存活率高的情況下復制的對象多,效率低。
HotSpot內存分配:Eden :Survivor:Survivor = 8:1:1。每次只有10%的內存是可能被浪費的。
標記整理算法。
分代收集算法。
根據對象的存活周期將內存分為新生代和老年代。
新生代中的對象都是朝生夕死的對象,老年代中的對象相對比較穩定。
新生代和老年代采用不同的收集算法。新生代的特點對象存活率很低(復制算法);老年代的特點對象存活率高,沒有額外的空間進行分配擔保(標記整理算法)。
哪些對象進入老年代?
A.大對象。
B.每次Eden進行MinorGC后對象年齡加1進入survivor,對象年齡達到15時進入老年代。
C.如果Survivor空間中相同年齡所有對象大小的總和大于survivor空間的一半,年齡大于等于該年齡的對象就直接進入老年代。
D.如果survivor空間不能容納Eden中存活的對象。由于擔保機制會進入老年代。如果survivor中的對象存活很多,擔保失敗,那么會進行一次Full GC。