相關文章
Java虛擬機系列javascript
這一節咱們來簡單的介紹垃圾收集器,並學習垃圾標記的算法:引用計數算法和根搜索算法,爲了更好的理解根搜索算法,會在文章的最後介紹Java對象在虛擬機中的生命週期。html
垃圾收集器(Garbage Collection),一般被稱做GC。提到GC,不少人認爲它是伴隨Java而出現的,其實GC出現的時間要比Java早太多了,它是1960誕生於MIT的Lisp。
GC主要作了兩個工做,一個是內存的劃分和分配,一個是對垃圾進行回收。關於內存的劃分和分配,目前Java虛擬機內存的劃分是依賴於GC的的設計的,好比如今GC都是採用了分代收集算法來回收垃圾,Java堆做爲GC主要管理的區域,被細分爲新生代和老年代,再細緻一點新生代又能夠劃分爲Eden空間、From Survivor空間、To Survivor空間等,這樣進行劃分是爲了更快的進行內存分配和回收。空間劃分後,GC就能夠爲新對象分配內存空間。
關於對垃圾進行回收,被引用的對象是存活的對象,而不被引用的對象是死亡的對象也就是垃圾,GC要區分出存活的對象和死亡的對象,也就是垃圾標記,並對垃圾進行回收。接下來咱們先來介紹垃圾標記算法。java
在對垃圾進行回收前,GC要先標記出垃圾,那麼如何標記呢,目前有兩種垃圾標記算法,分別是引用計數算法和根搜索算法,這兩個算法都和引用有些關聯,所以講垃圾標記算法前,咱們先回顧下引用的知識。算法
在JDK1.2以後,Java將引用分爲強引用、軟引用、弱引用和虛引用。性能優化
引用計數算法的基本思想就是每一個對象都有一個引用計數器,當對象在某處被引用的時候,它的引用計數器就加1,引用失效時就減1。當引用計數器中的值變爲0,則該對象就不能被使用成了垃圾。
目前主流的Java虛擬機沒有選擇引用計數算法來爲垃圾標記,主要緣由是引用計數算法沒有解決對象之間相互循環引用的問題。
舉個例子,下面代碼的註釋1和註釋2處,d1和d2相互引用,除此以外這兩個對象無任何其餘引用,實際上這兩個對象已經死亡,應該做爲垃圾被回收,可是因爲這兩個對象互相引用,引用計數就不會爲0,垃圾收集器就沒法回收它們。微信
class _2MB_Data {
public Object instance = null;
private byte[] data = new byte[2 * 1024 * 1024];//用來佔內存,測試垃圾回收
}
public class ReferenceGC {
public static void main(String[] args) {
_2MB_Data d1 = new _2MB_Data();
_2MB_Data d2 = new _2MB_Data();
d1.instance = d2;//1
d2.instance = d1;//2
d1 = null;
d2 = null;
System.gc();
}
}複製代碼
若是你使用Android Studio,就在Edit Configurations中的VM options加入以下語句來輸出詳細的GC日誌:jsp
-XX:+PrintGCDetails複製代碼
運行程序,GC日誌爲:
[GC (System.gc()) [PSYoungGen: 8028K->832K(76288K)] 8028K->840K(251392K), 0.0078334 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
[Full GC (System.gc()) [PSYoungGen: 832K->0K(76288K)] [ParOldGen: 8K->603K(175104K)] 840K->603K(251392K), [Metaspace: 3015K->3015K(1056768K)], 0.0045844 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 76288K, used 1966K [0x000000076af80000, 0x0000000770480000, 0x00000007c0000000)
eden space 65536K, 3% used [0x000000076af80000,0x000000076b16bac0,0x000000076ef80000)
from space 10752K, 0% used [0x000000076ef80000,0x000000076ef80000,0x000000076fa00000)
to space 10752K, 0% used [0x000000076fa00000,0x000000076fa00000,0x0000000770480000)
ParOldGen total 175104K, used 603K [0x00000006c0e00000, 0x00000006cb900000, 0x000000076af80000)
object space 175104K, 0% used [0x00000006c0e00000,0x00000006c0e96d10,0x00000006cb900000)
Metaspace used 3046K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 334K, capacity 388K, committed 512K, reserved 1048576K性能
查看此GC日誌前咱們先來簡單瞭解下各參數的含義,[GC (System.gc()和[Full GC (System.gc()說明了此次垃圾收集的停頓類型,而不是來區分新生代GC和老年代GC的。 [Full GC (System.gc() 說明此次GC發生了STW,STW也就是Stop the World機制,意思是說在執行垃圾收集算法時,只有GC線程在運行,其餘的線程則會所有暫停,等待GC線程執行完畢後才能再次運行。
PSYoungGen表明新生代,ParOldGen表明老年代,Metaspace表明元空間(JDK 8中用來替代永久代PermGen)。
咱們來看日誌的[GC (System.gc()),內存變化爲:8028K->840K(251392K),8028K表明回收前的內存大小,840K表明回收後的內存大小,251392K表明內存總大小。所以能夠得知內存回收大小爲(8028-840)K。這就說明JDK8的HotSpot虛擬機並無採用引用計數算法來標記內存,它對上述代碼中的兩個死亡對象的引用進行了回收。學習
這個算法的基本思想就是選定一些對象做爲GC Roots,並組成根對象集合,而後從這些做爲GC Roots的對象做爲起始點,向下進行搜索,若是目標對象到GC Roots是鏈接着的,咱們則稱該目標對象是可達的,若是目標對象不可達則說明目標對象是能夠被回收的對象,以下圖所示。
測試
從上圖看以看出,Obj五、Obj6和Obj7都是不可達的對象,其中Obj5和Obj6雖然互相引用,可是由於他們到GC Roots是不可達的因此它們仍舊會斷定爲可回收的對象,這樣根搜索算法就解決了引用計數算法沒法解決的問題:已經死亡的對象由於相互引用而不能被回收。
在Java中,能夠做爲GC Roots的對象主要有如下幾種:
還有一個問題是被標記爲不可達的對象會當即被垃圾收集器回收嗎?要回答這個問題咱們首先要了解Java對象在虛擬機中的生命週期。
當Java對象被類加載器加載到虛擬機中後,Java對象在Java虛擬機中有7個階段。
1.建立階段(Created)
建立階段的具體步驟爲:
2.應用階段(In Use)
當對象被建立,並分配給變量賦值,狀態就切換到了應用階段。
這一階段的對象至少要具備一個強引用,或者顯式的使用軟引用、弱引用或者虛引用。
3.不可見階段(Invisible)
程序中找不到對象的任何強引用,好比程序的執行已經超出了該對象的做用域。在不可見階段,對象仍可能被特殊的強引用GC Roots持有着,好比對象被本地方法棧中JNI引用或是被運行中的線程引用等。
4.不可達階段(Unreachable)
程序中找不到對象的任何強引用,而且垃圾收集器發現對象不可達。
5.收集階段(Collected)
垃圾收集器已經發現對象不可達,而且垃圾收集器已經準備好要對該對象的內存空間從新進行分配時。這個時候若是該對象重寫了finalize方法,則會調用該方法。
6.終結階段(Finalized)
當對象執行完finalize法後仍然處於不可達狀態時,或者對象沒有重寫finalize方法,則該對象進入終結階段,並等待垃圾收集器回收該對象空間。
7.對象空間從新分配階段(Deallocated)
當垃圾收集器對對象的內存空間進行回收或者再分配時,這個對象就會完全消失。
好了,咱們已經瞭解了Java對象在虛擬機中的生命週期,再來回想我方纔說的問題:被標記爲不可達的對象會當即被垃圾收集器回收嗎?很顯然是不會的,被標記爲不可達的對象會進入收集階段,這時會執行該對象重寫的finalize方法,若是沒有重寫finalize方法或者finalize方法中沒有從新與一個可達的對象進行關聯纔會進入終結階段,並最終被回收。
參考資料
《深刻理解 Java 虛擬機:JVM 高級特性與最佳實踐》第二版
《Java虛擬機精講》
《HotSpot實戰》
《Android應用性能優化最佳實踐》
JVM 深刻筆記(3)垃圾標記算法
GC roots
Java GC - 監控回收行爲與日誌分析
Java:對象的強、軟、弱和虛引用
JVM GC中Stop the world案例實戰
Java對象的生命週期
歡迎關注個人微信公衆號,第一時間得到博客更新提醒,以及更多成體系的Android相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,便可關注。