程式計數器、虛擬機棧、本地方法棧三個區域隨著線程的創建而創建、執行完成銷毀,棧中的棧幀隨著放大的進入和退出執行入棧與出棧,每個棧幀分配多少記憶體基本上是在類結構確定下來時已知,因此這幾個區域的記憶體分配與回收都具備確定性。Java堆中存放的所有對象的實例,只有在程式運行期間我們才會知道會創建哪些對象,這 ...
程式計數器、虛擬機棧、本地方法棧三個區域隨著線程的創建而創建、執行完成銷毀,棧中的棧幀隨著放大的進入和退出執行入棧與出棧,每個棧幀分配多少記憶體基本上是在類結構確定下來時已知,因此這幾個區域的記憶體分配與回收都具備確定性。
Java堆中存放的所有對象的實例,只有在程式運行期間我們才會知道會創建哪些對象,這部分記憶體分配與回收都是動態的,垃圾收集器重點關註的就是這部分。
引入計數算數
給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器都為0的的對象就是不可能再被使用的。
缺點:它很難解決對象之間的相互迴圈引用的問題。
VM ages:-XX:+PrintGCDetails 列印GC詳細信息:
package memory; public class ReferenceCountingGC { public Object instance = null; private static final int _1MB = 1024 *1024; private byte[] bigSize = new byte[2 *_1MB]; public static void main(String[] arg){ ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; System.gc(); } }
日誌:
[15.841s][info ][gc,start ] GC(0) Pause Full (System.gc()) [15.842s][info ][gc,task ] GC(0) Using 3 workers of 8 for full compaction [15.843s][info ][gc,phases,start] GC(0) Phase 1: Mark live objects [15.847s][info ][gc,phases ] GC(0) Phase 1: Mark live objects 4.412ms [15.848s][info ][gc,phases,start] GC(0) Phase 2: Prepare for compaction [15.849s][info ][gc,phases ] GC(0) Phase 2: Prepare for compaction 1.244ms [15.849s][info ][gc,phases,start] GC(0) Phase 3: Adjust pointers [15.851s][info ][gc,phases ] GC(0) Phase 3: Adjust pointers 1.951ms [15.851s][info ][gc,phases,start] GC(0) Phase 4: Compact heap [15.853s][info ][gc,phases ] GC(0) Phase 4: Compact heap 1.987ms [15.857s][info ][gc,heap ] GC(0) Eden regions: 4->0(9) [15.857s][info ][gc,heap ] GC(0) Survivor regions: 0->0(0) [15.857s][info ][gc,heap ] GC(0) Old regions: 0->3 [15.857s][info ][gc,heap ] GC(0) Archive regions: 0->0 [15.857s][info ][gc,heap ] GC(0) Humongous regions: 6->6 [15.857s][info ][gc,metaspace ] GC(0) Metaspace: 424K(640K)->424K(640K) NonClass: 399K(512K)->399K(512K) Class: 24K(128K)->24K(128K) [15.857s][info ][gc ] GC(0) Pause Full (System.gc()) 9M->6M(30M) 15.980ms [15.857s][info ][gc,cpu ] GC(0) User=0.02s Sys=0.02s Real=0.02s [17.684s][info ][gc,heap,exit ] Heap [17.684s][info ][gc,heap,exit ] garbage-first heap total 30720K, used 7088K [0x0000000081800000, 0x0000000100000000) [17.684s][info ][gc,heap,exit ] region size 1024K, 1 young (1024K), 0 survivors (0K) [17.684s][info ][gc,heap,exit ] Metaspace used 425K, committed 640K, reserved 1114112K [17.684s][info ][gc,heap,exit ] class space used 24K, committed 128K, reserved 1048576K
從日誌看出,記憶體進行了回收,說明JVM 的GC使用的不是引用計數演算法。
根搜索演算法
通過一系列的名為 “GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象的GC Roots 沒有任何引用鏈相連時,則證明此對象是不可用的。
引用
引用分為:
- 強引用(Strong Reference) :只要強引用還在,垃圾收集器永遠不會回收掉引用的對象。
- 軟引用(Soft Reference):在系統將要發生記憶體溢出異常之前,將會把這些對象列進回收範圍之中併進行第二次回收。如果這次回收還沒有足夠的記憶體,才會拋出記憶體溢出異常。
- 弱引用(Weak Reference):被弱引用關聯的對象只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前記憶體是否足夠,都會回收掉只被弱引用關聯的對象。
- 虛引用(Phantom Reference)(幽靈引用、幻影引用):一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。為一個對象設置虛引用關聯的唯一目的就是希望能在這個對象被收集器回收時收到一個系統通知。
在跟搜索演算法中不可達的對象,至少要經歷兩次標記過程:如果對象在進行根搜索後發現沒有與GC Roots相連的引用鏈,那它將會被第一次標記併進行一次篩選,篩選條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過,虛擬機將這兩種情況視為“沒有必要執行”。
如果對象有必要執行finalize()方法,那麼這個對象將被放置在一個F-Queue的隊列之中由Finalizer線程(虛擬機建立並出發)執行。finalizer用於告訴垃圾回收器下一步應該執行的操作。然後,GC將對F-Queue中的對象進行二次小規模的標記。
package memory; public class FinalizeEscapeGC { public static FinalizeEscapeGC SAVE_HOOK = null; public void isAlive(){ System.out.println("yes, i am still alive;"); } @Override protected void finalize() throws Throwable{ super.finalize(); System.out.println("fialize mehtod executed!"); //重新引用 FinalizeEscapeGC.SAVE_HOOK = this; } public static void main(String[] args) throws Throwable{ SAVE_HOOK = new FinalizeEscapeGC(); //對象第一次成功拯救自己 SAVE_HOOK = null; System.gc(); //因為Finalizer方法優先順序很低,暫停500毫秒,等它執行 Thread.sleep(500); if(SAVE_HOOK != null){ SAVE_HOOK.isAlive(); } else { System.out.println("no ,i am dead"); } //下麵這段代碼與上面的完全相同,但是這次自救失敗~ SAVE_HOOK = null; System.gc(); Thread.sleep(500); if(SAVE_HOOK != null){ SAVE_HOOK.isAlive(); } else { System.out.println("no ,i am dead !!!"); } } }
運行結果:
fialize mehtod executed!
yes, i am still alive;
no ,i am dead !!!
任何一個對象的finalize()方法都只會被系統自動調用一次,如果對象面臨下一次回收,他的finalize()方法不會被再次執行。
finalize()能做的所有工作,使用try-finally或者其他方式都可以做得更好、更及時。
回收方法區
- 回收的主要內容為:廢棄常量和無用的類
- 廢棄常量:沒有任何對象引用常量池中的常量,也沒有其他地方引用這個字面量。
- 無用類判定條件:
- 該類所有的實例都已經被回收,也就是Java堆中不存在該類的任何實例。
- 載入該類的ClassLoader已經被回收。
- 該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
垃圾收集演算法
標記-清除演算法
首先標記出所有需要回收的對象,在標記完成後同意回收掉所有被標記的對象。
缺點:
- 一個是效率問題,標記和清除過程的效率都不高;
- 另一個是空間問題,標記清除之後會產生大量不連續的記憶體碎片,空間碎片太多可能會導致程式在需要分配較大對象時無法找到連續的記憶體,而不得不提前觸發另一次垃圾收集動作。
複製演算法
將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊,當這一塊記憶體用完後,就將還存活的對象複製到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉。
缺點:將記憶體縮小為原來的一半。
現在商業虛擬機都採用這種收機演算法來回收新生代,將記憶體分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中的一塊Survivor。當回收時,將Eden和Survivor中還存活的對象一次性拷貝到另外一塊Survivor空間上,最後清理掉Eden和剛纔用過的Survivor空間。
這裡有個問題,無法保證回收後存活對象一塊Survivor空間夠用,所以這裡需要依賴其他記憶體(老年代)進行分配擔保。
標記-整理演算法
根據老年代的特點,對存活對象進行標記,讓所有存活對象向一端移動,然後直接清理掉端邊以外的記憶體。
分代收機演算法
根據對象噸貨周期的不同將記憶體劃分為幾塊,JAVA一般將堆分為新生代和老生代,這樣就可以根據各個年代的特點採用最適當的收集演算法。
垃圾收集器
Serial收集器
是最基本、歷史最悠久的收集器,是一個單線程收集器,在進行垃圾收集的時候,會暫停掉其他的所有工作線程。(簡單而高效)
ParNew收集器
Serial收集器的多線程版,除了使用多線程進行垃圾收集外,其餘的與Serial收集器一致。關註點是儘可能縮短用戶線程停頓時間。
Parallel Scavenge 收集器
新生代收集器,他也是使用複製演算法的收集器,也是並行的多線程收集器。目標是達到一個可控制的吞吐量。自適應調節策略。
吞吐量 = 運行用戶代碼的時間/(運行用戶代碼時間+垃圾收集時間)
停頓時間越短就越需要與用戶交互的程式,良好的響應速度能提升用戶體驗;高吞吐量則可以更高效率地利用CPU時間,儘快完成程式的運算任務,主要適用於後臺運算而不需要太多交互的任務。
Serial Old收集器
Serial Old是Serial收集器的老年代版本,使用“標記-整理”演算法。
Parallel Old收集器
是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”演算法。
CMS收集器
CMS收集器是一種以獲取最短回收停頓時間為目標的收集器。使用“標記-清除”演算法實現。
步驟:
- 初始標記
- 併發標記
- 重新標記
- 併發清除
- 其中初始標記、重新標記兩個步驟需要用戶工作線程暫停,初始標記只標記GC Roots能直接關聯到的對象,併發標記階段進行 GC Root Tracing 過程,重新標記階段則是為修正併發標記期間,用戶程式繼續運行而導致標記變化產生變動的那部分對象的標記記錄。
- 缺點:
- CMS收集器對CPU資源非常敏感。CMS預設用的回收線程數(CPU數量+3)/4。為解決該問題,虛擬機提供了一種i-CMS(增量式併發收集器)的CMS收集器變種,工作方式就是在併發標記和併發清理的時候讓GC線程與用戶線程交替運行,儘量減少GC線程獨占資源的時間,這樣整個垃圾收集的過程會更長,但對用戶程式的影響就會顯得少一些。
- CMS收集器無法處理浮動垃圾。如果CMS運行期預留的記憶體無法滿足程式需要,就會出現一次“Concurrent Mode Failure”失敗,這時候虛擬機將啟動後備元:臨時啟動Serial Old 收集器重新進行老年代的垃圾收集。
- CMS是一款基於“標記-清除”演算法實現的收集器,收集結束時會產生大量的空間碎片。
G1 收集器
- G1收集器是基於“標記-整理”演算法實現的收集器,也就是說它不會產生空間碎片,這對於長時間運行的應用系統來說非常重要。
- 它可以非常精準地控制停頓,既能讓使用者明確指定在一個長度為M毫秒的時間片段,消耗在垃圾收集上的時間不得超過N毫秒。
G1將正好Java堆(包括新生代、老年代)劃分為多個大小固定的獨立區域,並且跟蹤這些區域裡面的垃圾堆積程度,在後臺維護一個優先列表,每次根據允許的收集時間,有限回抽垃圾最多的區域。
記憶體分配和回收策略
對象優先在Eden分配
多數情況下,對象在新生代Eden區中扽配,當Eden區域沒有足夠的空間分配時,虛擬機將發起一次Minor GC。
package memory; public class JavaEdenTest { private static final int _1MB = 1024 *1024; public static void testAllocation(){ byte[] allocation1,allocation2,allocation3,allocation4; allocation1 = new byte[2 *_1MB]; allocation2 = new byte[2 * _1MB]; allocation3 = new byte[2 *_1MB]; allocation4 = new byte[4 *_1MB]; } /** * -verbose:gc * -Xms20M -Xmx20M -Xmn10M 顯示JAVA堆的大小20M且不可擴展 * 其中10M分配給新生代,剩餘10M分配給老年代 * -XX:SurvivorRatio=8 確定新生代中的Eden區與一個Survivor區域的空間比例為8:1 * -XX:+PrintGCDetails 列印詳細的收集器日誌參數 * * @param args */ public static void main(String[] args){ testAllocation(); } }
運行日誌:
[16.630s][info ][gc,start ] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation) [16.631s][info ][gc,task ] GC(0) Using 2 workers of 8 for evacuation [16.639s][info ][gc,phases ] GC(0) Pre Evacuate Collection Set: 0.3ms [16.639s][info ][gc,phases ] GC(0) Merge Heap Roots: 0.1ms [16.639s][info ][gc,phases ] GC(0) Evacuate Collection Set: 4.8ms [16.639s][info ][gc,phases ] GC(0) Post Evacuate Collection Set: 2.1ms [16.639s][info ][gc,phases ] GC(0) Other: 0.8ms [16.639s][info ][gc,heap ] GC(0) Eden regions: 3->0(9) [16.639s][info ][gc,heap ] GC(0) Survivor regions: 0->1(2) [16.639s][info ][gc,heap ] GC(0) Old regions: 0->0 [16.639s][info ][gc,heap ] GC(0) Archive regions: 0->0 [16.639s][info ][gc,heap ] GC(0) Humongous regions: 9->9 [16.639s][info ][gc,metaspace] GC(0) Metaspace: 419K(576K)->419K(576K) NonClass: 394K(448K)->394K(448K) Class: 24K(128K)->24K(128K) [16.639s][info ][gc ] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation) 11M->10M(20M) 9.150ms [16.639s][info ][gc,cpu ] GC(0) User=0.00s Sys=0.00s Real=0.01s [16.640s][info ][gc ] GC(1) Concurrent Undo Cycle [16.640s][info ][gc,marking ] GC(1) Concurrent Cleanup for Next Mark [16.640s][info ][gc,marking ] GC(1) Concurrent Cleanup for Next Mark 0.402ms [16.640s][info ][gc ] GC(1) Concurrent Undo Cycle 0.770ms
allocation4在實例化時,出發了一次Minor GC。
大對象直接進入老年代
VM參數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:PretenureSizeThreshold=3145728
-XX:PretenureSizeThreshold的作用是令大於這個設置值的對象直接進入老年代中分配。
避免在Eden區及兩個Survivor區之間發生大量的記憶體拷貝。
package memory; public class JavaTestBigObjectGC { private static final int _1MB = 1024 * 1024; public static void main(String[] args){ byte[] allocation; allocation = new byte[4 * _1MB]; } }
運行日誌:
[5.942s][info ][gc,heap,exit] Heap [5.942s][info ][gc,heap,exit] garbage-first heap total 20480K, used 7586K [0x00000000fec00000, 0x0000000100000000) [5.942s][info ][gc,heap,exit] region size 1024K, 3 young (3072K), 0 survivors (0K) [5.942s][info ][gc,heap,exit] Metaspace used 419K, committed 576K, reserved 1114112K [5.942s][info ][gc,heap,exit] class space used 24K, committed 128K, reserved 1048576K
長期存活對象進入老年代
虛擬機給每個對象定義了一個對象年齡計數器,如果對象在Eden中生成,沒經過一次Minor GC後仍然存活,並且能被Survivor容納的話,將會被移動到Survivor空間中,並將對象年齡設置為1,當年齡增加到一定程度(預設為15歲)時,就會被晉升到老年代中。
對象寄生老年的年齡閾值,可以使用-XX:MaxTenuringThreshold來設置。
package memory; public class JavaTestTenuringThreshold { private static final int _1MB = 1024 *1024; /** * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 * -XX:MaxTenuringThreshold=1 */ public static void testTenuringThreshold(){ byte[] allocation1,allocation2,allocation3; allocation1 = new byte[4 * _1MB]; allocation2 = new byte[4 * _1MB]; allocation3 = new byte[4* _1MB]; allocation3 = null; allocation3 = new byte[4*_1MB]; } public static void main(String[] args){ testTenuringThreshold(); } }
[6.680s][info ][gc,start ] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation) [6.680s][info ][gc,task ] GC(0) Using 2 workers of 8 for evacuation [6.688s][info ][gc,phases ] GC(0) Pre Evacuate Collection Set: 0.4ms [6.688s][info ][gc,phases ] GC(0) Merge Heap Roots: 0.2ms [6.688s][info ][gc,phases ] GC(0) Evacuate Collection Set: 4.8ms [6.688s][info ][gc,phases ] GC(0) Post Evacuate Collection Set: 1.8ms [6.688s][info ][gc,phases ] GC(0) Other: 0.7ms [6.688s][info ][gc,heap ] GC(0) Eden regions: 3->0(9) [6.688s][info ][gc,heap ] GC(0) Survivor regions: 0->1(2) [6.688s][info ][gc,heap ] GC(0) Old regions: 0->0 [6.688s][info ][gc,heap ] GC(0) Archive regions: 0->0 [6.688s][info ][gc,heap ] GC(0) Humongous regions: 5->5 [6.688s][info ][gc,metaspace] GC(0) Metaspace: 423K(640K)->423K(640K) NonClass: 399K(512K)->399K(512K) Class: 24K(128K)->24K(128K) [6.688s][info ][gc ] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation) 7M->6M(20M) 8.803ms [6.688s][info ][gc,cpu ] GC(0) User=0.02s Sys=0.02s Real=0.01s [6.689s][info ][gc ] GC(1) Concurrent Undo Cycle [6.689s][info ][gc,marking ] GC(1) Concurrent Cleanup for Next Mark [6.689s][info ][gc,marking ] GC(1) Concurrent Cleanup for Next Mark 0.187ms [6.689s][info ][gc ] GC(1) Concurrent Undo Cycle 0.494ms [6.696s][info ][gc,start ] GC(2) Pause Young (Concurrent Start) (G1 Humongous Allocation) [6.696s][info ][gc,task ] GC(2) Using 2 workers of 8 for evacuation [6.699s][info ][gc,phases ] GC(2) Pre Evacuate Collection Set: 0.2ms [6.699s][info ][gc,phases ] GC(2) Merge Heap Roots: 0.1ms [6.699s][info ][gc,phases ] GC(2) Evacuate Collection Set: 1.9ms [6.699s][info ][gc,phases ] GC(2) Post Evacuate Collection Set: 0.5ms [6.699s][info ][gc,phases ] GC(2) Other: 0.1ms [6.699s][info ][gc,heap ] GC(2) Eden regions: 0->0(10) [6.699s][info ][gc,heap ] GC(2) Survivor regions: 1->0(2) [6.699s][info ][gc,heap ] GC(2) Old regions: 0->1 [6.699s][info ][gc,heap ] GC(2) Archive regions: 0->0 [6.699s][info ][gc,heap ] GC(2) Humongous regions: 10->10 [6.699s][info ][gc,metaspace] GC(2) Metaspace: 424K(640K)->424K(640K) NonClass: 399K(512K)->399K(512K) Class: 24K(128K)->24K(128K) [6.699s][info ][gc ] GC(2) Pause Young (Concurrent Start) (G1 Humongous Allocation) 11M->10M(20M) 3.165ms [6.699s][info ][gc,cpu ] GC(2) User=0.02s Sys=0.02s Real=0.00s [6.699s][info ][gc ] GC(3) Concurrent Mark Cycle [6.699s][info ][gc,marking ] GC(3) Concurrent Clear Claimed Marks [6.699s][info ][gc,marking ] GC(3) Concurrent Clear Claimed Marks 0.011ms [6.699s][info ][gc,marking ] GC(3) Concurrent Scan Root Regions [6.700s][info ][gc,marking ] GC(3) Concurrent Scan Root Regions 0.900ms [6.700s][info ][gc,marking ] GC(3) Concurrent Mark [6.700s][info ][gc,marking ] GC(3) Concurrent Mark From Roots [6.700s][info ][gc,task ] GC(3) Using 2 workers of 2 for marking [6.701s][info ][gc,marking ] GC(3) Concurrent Mark From Roots 0.666ms [6.701s][info ][gc,marking ] GC(3) Concurrent Preclean [6.701s][info ][gc,marking ] GC(3) Concurrent Preclean 0.013ms [6.715s][info ][gc,start ] GC(3) Pause Remark [6.716s][info ][gc ] GC(3) Pause Remark 15M->15M(20M) 0.810ms [6.716s][info ][gc,cpu ] GC(3) User=0.00s Sys=0.00s Real=0.00s [6.716s][info ][gc,marking ] GC(3) Concurrent Mark 16.267ms [6.716s][info ][gc,marking ] GC(3) Concurrent Rebuild Remembered Sets [6.716s][info ][gc,marking ] GC(3) Concurrent Rebuild Remembered Sets 0.007ms [6.717s][info ][gc,start ] GC(3) Pause Cleanup [6.717s][info ][gc ] GC(3) Pause Cleanup 15M->15M(20M) 0.019ms [6.717s][info ][gc,cpu ] GC(3) User=0.00s Sys=0.00s Real=0.00s [6.717s][info ][gc,marking ] GC(3) Concurrent Cleanup for Next Mark [6.717s][info ][gc,start ] GC(4) Pause Young (Normal) (G1 Humongous Allocation) [6.717s][info ][gc,task ] GC(4) Using 2 workers of 8 for evacuation [6.718s][info ][gc,phases ] GC(4) Pre Evacuate Collection Set: 0.1ms [6.718s][info ][gc,phases ] GC(4) Merge Heap Roots: 0.1ms [6.718s][info ][gc,phases ] GC(4) Evacuate Collection Set: 0.1ms [6.718s][info ][gc,phases ] GC(4) Post Evacuate Collection Set: 0.4ms [6.718s][info ][gc,phases ] GC(4) Other: 0.0ms [6.718s][info ][gc,heap ] GC(4) Eden regions: 0->0(10) [6.718s][info ][gc,heap ] GC(4) Survivor regions: 0->0(2) [6.718s][info ][gc,heap ] GC(4) Old regions: 1->1 [6.718s][info ][gc,heap ] GC(4) Archive regions: 0->0 [6.718s][info ][gc,heap ] GC(4) Humongous regions: 15->10 [6.718s][info ][gc,metaspace] GC(4) Metaspace: 424K(640K)->424K(640K) NonClass: 399K(512K)->399K(512K) Class: 24K(128K)->24K(128K) [6.718s][info ][gc ] GC(4) Pause Young (Normal) (G1 Humongous Allocation) 15M->10M(20M) 0.861ms [6.718s][info ][gc,cpu ] GC(4) User=0.00s Sys=0.00s Real=0.00s [6.718s][info ][gc,marking ] GC(3) Concurrent Cleanup for Next Mark 1.440ms [6.718s][info ][gc ] GC(3) Concurrent Mark Cycle 19.012ms
動態對象年齡判定
為了能更好地適應不同程式的記憶體情況,虛擬機並不總是要求對象的年齡必須達到MaxTenuringshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就可以直接進入老年代。
空間擔保
在發生Minor GC時,虛擬機會在檢測之前每次晉升到老年代的平均大小是否大於老年代的剩餘空間大小,如果大於,則改為直接進行一次Full GC。如果小於,則查看HandlePromotionFailure設置是否允許擔保失敗;如果允許,那隻會進行Minor GC;如果不允許,則也要改為進行一次 Full GC。