垃圾回收(GC)

Garbage Collection
垃圾收集需要完成的三件事
1、哪些內存需要回收
2、什麼時候回收
3、如何回收

java內存運行時區域的部分中,程序計數器,虛擬機棧,本地方法棧三個區域隨線程而生而滅,棧中的棧偵隨方法的進出而有條不紊的執行出棧入棧操作。每個棧中分配多少內存基本上在類結構確定下來時就已知的,因此,這些部分是內存分配和回收都具確定性。

java堆和方法區不一樣。一接口實現,一方法的分支等需要的內存可能不一樣,我們只有在運行時才知道要創建哪些對象,這部分都是動態的,GC關注的就是這部分內存.

判斷對象已死
1、引用計數算法
引用計數的實現簡單,效率也高,但是java沒有采用這種方式,原因是很難解決對象之間的相互循環引用的問題
-verbose:gc -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:SurvivorRatio=8
public class ReferenceCountingGC {
public Object instace = null;
private static final int _1MB = 1024*1024;
private byte [] bigSize = new byte[2* _1MB];
public static void main(String[] args) {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA. instace = objB;
objB. instace = objA;
objA = null;
objB = null;
System. gc();
}
}
2、根搜索算法
主流商用程序語言中,都使用根搜索算法。基本思路是通過一系列名爲“GC Root”的對象作爲起始點,從這些節點開始向下搜索,搜索走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連,則爲不可用。
java中可作爲GC Roots的對象包括
1.虛擬機棧中的引用的對象
2.方法區中的類靜態屬性引用的對象
3.方法區中常量引用的對象
4.本地方法棧中JNI(即一般說的native方法)的引用的對象
3.引用概念的擴充
從JDK1.2之後,java對引用的概念進行了擴充,將引用分爲:
強引用(Strong Reference):
軟引用(Soft Reference):當內存溢出異常之前,會把這些對象列進回收範圍之中
弱引用(Weak Reference): 被弱引用關聯的對象只能生存到下一次垃圾收集。
虛引用(Phantom Reference): 爲一個對象設置虛引用的關聯的唯一目的就是希望能在對象被收集器回收時收到一個系統的通知

死亡過程
當被搜索到對象不可達GC Roots的時候,該對象會進入兩次的標記過程。
判定它爲不可達之後,標記一次。
然後篩選有必要執行finalize方法的對象,條件爲該對象已經覆蓋finalize()方法,或者finalize方法還未被執行
放進F-Queue隊列中,之後由一條虛擬機自動建立,低優先級的Finalizer線程去執行(觸發)。但並不承諾會等待它運行結束(不然這個隊列都會在等它)。
在進行第二次標記之前,finalize方法是最後一次對象逃脫死亡的機會,只要重新與引用鏈上的任何一個對象建立關聯,那在第二次標記的時候它將被移除集合。但是finalize只被系統調用一次,所以對象面臨下一次回收,它的finalize不會再執行了
-verbose:gc -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:SurvivorRatio=8
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
private static final int _1MB = 1024*1024;
private byte [] bigSize = new byte[2* _1MB];
public void isAlive() {
System.out.println("yes, i am still alive" );
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed!" );
FinalizeEscapeGC. SAVE_HOOK = this ;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new FinalizeEscapeGC();
SAVE_HOOK = null ;
System. gc();
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" );
}
}
}
finalize其實是java剛誕生時爲了讓c++程序員更容易接受的一種妥協。
缺點:代價高,不確定性大,無法保證各個對象的調用順序。關於那它做外部資源的關閉,也是沒必要的,因爲try-finally就可以更好更及時做到。
結論:把finalize這東西給忘記吧

java虛擬機規範中說方法區(HotSpat虛擬機中的永久代)是可以不要求在方法區實現垃圾收集的,因爲在這裏收集垃圾“性價比”不高,在堆中,尤其是新生代,一次垃圾收集可以回收70-95%的空間,而永久代效率遠遠低於此。
永久代垃圾收集主要兩部分:
回收廢棄常量:和在堆中回收對象很像。
無用的類:1.該類的所有實例都被回收 2.加載該類的ClassLoader已經被回收 3.該類對應的java.lang.Class對象(Test.class)沒有下載任何地方被引用。如果這個類符合這三個條件,jvm就會在方法區垃圾回收的時候對類進行卸載(就是指清除類信息,類生命週期的結束,涉及到類的生命週期:加載、連接、初始化、使用、卸載)
在大量使用反射、動態代理、CGLib等bytecode框架的場景,以及動態生成jsp和OSGI這類頻繁自定義ClassLoader的場景都需要虛擬機具備類卸載的功能,保證永久代不會溢出

垃圾收集算法
標記-清除算法:只是標記,然後清除。最基礎的算法,但是兩個缺點:效率問題,空間問題(很多碎片,會導致找不到連續空間之後,會不得不提前出發另一次垃圾收集動作)
複製算法:大小相等的兩塊內存區域,當一塊用完之後,將活着的對象複製到另外一塊上面,然後一次性清理舊塊
現在的商業虛擬機都採用這種收集算法來回收新生代,但是IBM的專門研究表明,新生代中的對象98%是朝生夕死的,所有沒必要1:1的空間劃分。
而是將內存分爲一塊較大Eden空間和兩塊Survivor空間比例8:1,回收時,將Eden和其中一塊survivor裏面的存活對象拷貝到另外一塊Survivor。所有要保證存活對象不能多於10%,如果多了,需要依賴其他內存(老年代)進行分配擔保。
有個小缺點:在對象存活率較高的情況下,執行復制將會使效率變低
標記-整理算法:在新生代可以用複製算法,但是在老年代就不能直接拿來用。在標記之後,讓所有存活的對象往一端移動,然後直接清理掉邊界以外的內存。
分代收集算法:當前的商業虛擬機的垃圾收集都採用“分代收集(Generation Collection)”算法,根據對象存活的時間不同將內存劃分爲幾塊。一般分爲新生代和老年代。在新生代中,每收集一次發現有大量對象死亡,選複用算法。而老年代中對象存活率高、沒有額外空間給擔保,就必須使用“標記-清理”或“標記-整理”來進行回收。

java虛擬機規範中對垃圾收集器如何實現沒有任何規定,所以不同廠商、版本的虛擬機鎖提供的收集器都可能差別很大。一般會提供參數讓我媽選擇各個年代所使用的收集器。

基於HotSpot虛擬機1.6版所包含的收集器

* "Serial" is a stop-the-world, copying collector which uses a single GC thread.
* "ParNew" is a stop-the-world, copying collector which uses multiple GC threads. It differs from "Parallel Scavenge" in that it has enhancements that make it usable with CMS. For example, "ParNew" does the synchronization needed so that it can run during the concurrent phases of CMS.
* "Parallel Scavenge" is a stop-the-world, copying collector which uses multiple GC threads.
* "Serial Old" is a stop-the-world, mark-sweep-compact collector that uses a single GC thread.
* "CMS" is a mostly concurrent, low-pause collector.
* "Parallel Old" is a compacting collector that uses multiple GC threads.

Serial收集器
1、jdk1.3之前新生代唯一的選擇。
2、單線程收集器。
3、適合運行在Client模式下

ParNew收集器
1、是Serial的多線程版本,意思就是說除了多線程進行垃圾收集那一部分,其他的都和Serial一樣(代碼共用)
2、默認開啓的收集線程數與CPU的數量相同,在CPU非常多的情況下,可以使用-XX:ParallelGCThreads參數來限制垃圾收集的線程數

Parallel Scavenge收集器
1、關注點和上面兩者不一樣,關注於吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)。最高效的利用CPU時間,適合在後臺運算而不需要太多的交互任務(關注於儘可能縮短垃圾收集時用戶線程的停頓時間,適合用戶的交互)
2、無法與CMS配合使用
3、兩個參數精確控制吞吐量:控制最大垃圾收集停頓時間-XX:MaxGCPauseMillis;直接設置吞吐量大小-XX:GCTimeRatio。
停頓時間參數的縮短是以犧牲吞吐量和新生代空間來換取的:減少新生代,提高收集的頻繁。這樣空間小了,吞吐量小了,提頓時間也小了
4、開關參數:-XX:+UseAdaptive(與上述兩者重要的區別)自適應調節,打開後就不需要手動設置新生代大小、Eden與Survivor比例(-XX:SurvivorRatio)、晉升老年代對象的年齡(-XX:PretenureSizeThreshold)
http://docs.oracle.com/javase/1.5.0/docs/guide/vm/gc-ergonomics.html

Serial Old
Parallel Old
CMS(Concurrent Mark Sweep)
CMS收集器是以獲取最短回收停頓時間爲目標的收集器。
運作過程比較複雜 4步:
1、初始標記
僅僅標記GC Roots能直接關聯到的對象,時間很短,可見下面標紅
2、併發標記
GC Roots Tracing過程(根搜索算法;最耗時的過程,不過已經和用戶線程併發了)
3、重新標記
修正併發標記期間,因用戶程序繼續運作而導致標記產生變動的那一部分
4、併發清除

CMS收集器運作日誌
0.104: [GC [1 CMS-initial-mark: 32073K(57344K)] 32976K(64768K), 0.0005721 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Total time for which application threads were stopped: 0.0006903 seconds
0.104: [CMS-concurrent-mark-start]
0.105: [GC 0.105: [ParNew: 7424K->768K(7424K), 0.0080862 secs] 39497K->39446K(64768K), 0.0081270 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Total time for which application threads were stopped: 0.0082087 seconds
0.115: [GC 0.115: [ParNew: 7424K->768K(7424K), 0.0080369 secs] 46102K->46062K(64768K), 0.0080786 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Total time for which application threads were stopped: 0.0081630 seconds
0.124: [GC 0.124: [ParNew: 7424K->768K(7424K), 0.0081382 secs] 52718K->52680K(64768K), 0.0081783 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
Total time for which application threads were stopped: 0.0082574 seconds
0.139: [CMS-concurrent-mark: 0.010/0.034 secs] [Times: user=0.05 sys=0.00, real=0.03 secs]
0.139: [CMS-concurrent-preclean-start]
0.139: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.139: [CMS-concurrent-abortable-preclean-start]
0.139: [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.139: [GC[YG occupancy: 7198 K (7424 K)]0.139: [Rescan (parallel) , 0.0004863 secs]0.139: [weak refs processing, 0.0000033 secs] [1 CMS-remark: 51912K(57344K)] 59110K(64768K), 0.0005244 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Total time for which application threads were stopped: 0.0005979 seconds
0.139: [CMS-concurrent-sweep-start]
0.141: [CMS-concurrent-sweep: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.141: [CMS-concurrent-reset-start]
0.141: [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

缺點:
1、對CPU資源非常敏感,CMS默認啓動的回收線程是(CPU數量+3)/4
2、無法處理浮動垃圾,可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC 的產生
就是在併發清理階段,用戶線程還在運行,就會產生新的垃圾,只能是下一次GC的時候清理,這部分叫"浮動垃圾"。
所以這部分的浮動垃圾必須要有空間留着,如果剩餘的老年代空間無法滿足,就會報concurrent mode failure。
0.125: [GC 0.125: [ParNew: 7424K->768K(7424K), 0.0079396 secs] 52718K->52680K(64768K), 0.0079763 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Total time for which application threads were stopped: 0.0080435 seconds
(concurrent mode failure): 51912K->148K(57344K), 0.0119054 secs] 58977K->148K(64768K), [CMS Perm : 2106K->2105K(12288K)], 0.0119676 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Total time for which application threads were stopped: 0.0120395 seconds
這個時候,虛擬機將啓動後備預案,臨時啓動Serial Old收集器進行一次full gc 這個gc時間比起其他時間相當長
3、碎片過多
當找不到足夠大的連續空間的時候,不得不觸發Full GC。可以試着-XX:+UseCMSCompactAtFullCollection來使Full GC之後進行一次碎片整理(這個無法併發,還得stop),-XX:+CMSFullGCsBeforeCompaction設置在每執行多少次Full GC之後進行一次碎片整理

DefNew是HotSpot的GC框架裏serial GC裏的young gen空間。只對它的收集就是這個上下文裏的minor GC或者說young GC。

G1收集器
和CMS相比顯著的改進
1、因爲是基於“標記-整理”算法,不會產生空間碎片
2、精確控制停頓(它可以實現基本不犧牲吞吐量的情況下完成低停頓的內存回收),能讓使用者明確指定在一個長度M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒。
它將整個java堆劃分爲多個大小固定的獨立區域(Region),根據這些區域的垃圾程度,維護優先列表來優先回收垃圾最多的區域
發佈了28 篇原創文章 · 獲贊 0 · 訪問量 943
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章