二 JAVA垃圾收集器
2.1 垃圾收集簡史
垃圾收集提供了內存管理的機制,使得應用程序不需要在關注內存如何釋放,內存用完后,垃圾收集會進行收集,這樣就減輕了因為人為的管理內存而造成的 錯誤,比如在C++語言里,出現內存泄露時很常見的。Java語言是目前使用最多的依賴于垃圾收集器的語言,但是垃圾收集器策略從20世紀60年代就已經 流行起來了,比如Smalltalk,Eiffel等編程語言也集成了垃圾收集器的機制。
2.2 常見的垃圾收集策略

所有的垃圾收集算法都面臨同一個問題,那就是找出應用程序不可到達的內存塊,將其釋放,這里面得不可到達主要是指應用程序已經沒有內存塊的引用了, 而在JAVA中,某個對象對應用程序是可到達的是指:這個對象被根(根主要是指類的靜態變量,或者活躍在所有線程棧的對象的引用)引用或者對象被另一個可 到達的對象引用。
2.2.1 Reference Counting(引用計數)
引用計數是最簡單直接的一種方式,這種方式在每一個對象中增加一個引用的計數,這個計數代表當前程序有多少個引用引用了此對象,如果此對象的引用計數變為0,那么此對象就可以作為垃圾收集器的目標對象來收集。
優點:
簡單,直接,不需要暫停整個應用
缺點:
1.需要編譯器的配合,編譯器要生成特殊的指令來進行引用計數的操作,比如每次將對象賦值給新的引用,或者者對象的引用超出了作用域等。
2.不能處理循環引用的問題
2.2.2 跟蹤收集器
跟蹤收集器首先要暫停整個應用程序,然后開始從根對象掃描整個堆,判斷掃描的對象是否有對象引用,這里面有三個問題需要搞清楚:

1.如果每次掃描整個堆,那么勢必讓GC的時間變長,從而影響了應用本身的執行。因此在JVM里面采用了分代收集,在新生代收集的時候minor gc只需要掃描新生代,而不需要掃描老生代。
2.JVM采用了分代收集以后,minor gc只掃描新生代,但是minor gc怎么判斷是否有老生代的對象引用了新生代的對象,JVM采用了卡片標記的策略,卡片標記將老生代分成了一塊一塊的,劃分以后的每一個塊就叫做一個卡 片,JVM采用卡表維護了每一個塊的狀態,當JAVA程序運行的時候,如果發現老生代對象引用或者釋放了新生代對象的引用,那么就JVM就將卡表的狀態設 置為臟狀態,這樣每次minor gc的時候就會只掃描被標記為臟狀態的卡片,而不需要掃描整個堆。具體如下圖:
3.GC在收集一個對象的時候會判斷是否有引用指向對象,在JAVA中的引用主要有四種:Strong reference,Soft reference,Weak reference,Phantom reference.
◆ Strong Reference
強引用是JAVA中默認采用的一種方式,我們平時創建的引用都屬于強引用。如果一個對象沒有強引用,那么對象就會被回收。
- public void testStrongReference(){
- Object referent = new Object();
- Object strongReference = referent;
- referent = null;
- System.gc();
- assertNotNull(strongReference);
- }
◆ Soft Reference
軟引用的對象在GC的時候不會被回收,只有當內存不夠用的時候才會真正的回收,因此軟引用適合緩存的場合,這樣使得緩存中的對象可以盡量的再內存中待長久一點。
- Public void testSoftReference(){
- String str = "test";
- SoftReference<String> softreference = new SoftReference<String>(str);
- str=null;
- System.gc();
- assertNotNull(softreference.get());
- }
◆ Weak reference
弱引用有利于對象更快的被回收,假如一個對象沒有強引用只有弱引用,那么在GC后,這個對象肯定會被回收。
- Public void testWeakReference(){
- String str = "test";
- WeakReference<String> weakReference = new WeakReference<String>(str);
- str=null;
- System.gc();
- assertNull(weakReference.get());
- }
◆ Phantom reference
2.2.2.1 Mark-Sweep Collector(標記-清除收集器)
標記清除收集器最早由Lisp的發明人于1960年提出,標記清除收集器停止所有的工作,從根掃描每個活躍的對象,然后標記掃描過的對象,標記完成以后,清除那些沒有被標記的對象。
優點:
1 解決循環引用的問題
2 不需要編譯器的配合,從而就不執行額外的指令
缺點:
1.每個活躍的對象都要進行掃描,收集暫停的時間比較長。
2.2.2.2 Copying Collector(復制收集器)復制收集器將內存分為兩塊一樣大小空間,某一個時刻,只有一個空間處于活躍的狀態,當活躍的空間滿的時候,GC就會將活 躍的對象復制到未使用的空間中去,原來不活躍的空間就變為了活躍的空間。復制收集器具體過程可以參考下圖:

優點:
1 只掃描可以到達的對象,不需要掃描所有的對象,從而減少了應用暫停的時間
缺點:
1.需要額外的空間消耗,某一個時刻,總是有一塊內存處于未使用狀態
2.復制對象需要一定的開銷
2.2.2.3 Mark-Compact Collector(標記-整理收集器)標記整理收集器汲取了標記清除和復制收集器的優點,它分兩個階段執行,在第一個階段,首先掃描所有活躍的對象,并 標記所有活躍的對象,第二個階段首先清除未標記的對象,然后將活躍的的對象復制到堆得底部。標記整理收集器的過程示意圖請參考下圖:Mark- compact策略極大的減少了內存碎片,并且不需要像Copy Collector一樣需要兩倍的空間。