JVM是如何和“垃圾”發(fā)生關(guān)系的
本文轉(zhuǎn)載自微信公眾號「碼上Java」,作者碼上Java。轉(zhuǎn)載本文請聯(lián)系碼上Java公眾號。
在開始之前,我們先回顧一下堆是個什么玩意,大家可能都知道,我們每天創(chuàng)建的Java對象幾乎都存放在堆上面,所以說堆是一個巨大的對象池一點都不過分,在這個對象池里面管理者數(shù)據(jù)巨大的對象實例。
在對象池中對象的引用層次,有的是很深的。比如一個調(diào)用非常頻繁的接口,生產(chǎn)對象的速度是非??捎^的。對象之間的關(guān)系,可以形容成一張網(wǎng)。雖然Java總是給人一種有使不完的內(nèi)存的感覺,但是對象也不能一直增加不減少啊,所以就必須有垃圾回收這個操作。
那么JVM如何發(fā)現(xiàn)垃圾的呢?
"垃圾回收"本文中簡稱 GC
你還記得電視劇中的“誅九族""?
比如小憨批打了皇帝老兒一巴掌,把皇帝老兒打的鼻青臉腫滴,皇帝老兒非常生氣,他要下令誅小憨批的九族,以平心頭只恨。
哈哈哈嗝~ 小憨批完了~
那么我們看看在古代這個誅九族是具體操作的呢?首先需要追溯到共同的祖先(也就是小憨批家族的大哥大),再往下逐一細(xì)數(shù)和小憨批有關(guān)系的(小憨批真坑啊)。
其實發(fā)生在堆上的垃圾回收和這個“誅九族“的是相同思路,那么我們下面具體分析一下JVM是如何進(jìn)行GC的呢?
關(guān)于JVM的GC是不受程序控制的,當(dāng)滿足一定條件的時候就會主動觸發(fā)。
當(dāng)發(fā)生GC的時候,對于一個對象來說,JVM總能夠找到引用它的祖先,當(dāng)找到最后的時候,JVM發(fā)現(xiàn)這家伙的有些祖先已經(jīng)玩完了,那么它們就會被JVM給干掉。
為什么還有沒有被干掉的祖先呢?因為這些躲過GC的祖先們,它們是GC Roots ,長得比較特殊嘛(下面介紹它們的樣子)。
當(dāng)從GC Roots 向下追溯、搜索,就會產(chǎn)生一個引用鏈。當(dāng)碰到有對象沒有任何一個GC Roots 產(chǎn)生關(guān)系的話,這個對象就會被無情的干掉。(一根繩上的螞蚱嘛)
來,我們畫個圖瞅瞅咋回事,如下圖所示,Object5、Object6、Object7,由于不能和 GC Root 產(chǎn)生關(guān)聯(lián),發(fā)生 GC 時,就會被摧毀。
其實所謂的垃圾回收就是圍繞著GC Roots 來的,但是同時,GC Roots 也存在著很多內(nèi)存泄漏的根源,因為其他引用小弟壓根沒有這個權(quán)利。
你可能會產(chǎn)生疑問,那么什么樣的對象才會是GC Roots 呢?
這個不在于它是什么樣的對象,關(guān)鍵是它所處的位置(仔細(xì)品~)。
GC Roots 是什么
首先,GC Roots必須是一組必須活躍的引用。簡單的講,就是程序接下來通過直接引用或間接引用,能夠被訪問到的潛在被使用的對象(咋感覺還是有點繞呢)。
GC Roots 是這樣子滴:
- Java線程中,當(dāng)前所有正在被調(diào)用的方法的引用類型參數(shù)、局部變量、臨時值等等。也就是與我們棧幀相關(guān)的各種引用。
- 所有當(dāng)前被加載的Java類。
- Java類的引用類型靜態(tài)變量。
- 運行時常量池里的引用類型常量。
- JVM內(nèi)部數(shù)據(jù)結(jié)構(gòu)的一些引用,比如sun.jvm.hotspot.memory.Univers類。
- 用于同步的監(jiān)控對象。比如調(diào)用了對象的wait()方法。
- JNI handles,包括global handles 和 local handles。
以上GC Roots大致可以分為一下三大類。
- 活動線程相關(guān)的各種引用。
- 類的靜態(tài)變量的引用。
- JNI引用。
最后我們需要注意的是,我們這里說的是活躍的引用,而不是對象,對象是不能作為GC Roots的。
整個GC過程中是找到那些活對象,并把剩余的空間都認(rèn)得為“無用”。而不是找到所有死掉的對象,并回收它們占用的空間。所有說,哪怕JVM的堆非常大,基于tracing的GC方式,回收速度也是跟快的。
總結(jié)
GC Roots 就是可達(dá)性分析法。還有一種叫作引用計數(shù)法的方式。下面我們簡單介紹一下。
引用計數(shù)法:在Java中如果要操作對象,就必須先獲取該對象的引用,因此可以通過引用計數(shù)法來判斷一個對象是否可以被回收。在為一個對象添加一個引用時,引用計數(shù)器就加1;為對象刪除一個引用時,引用計數(shù)器就減1;如果一個對象的引用計數(shù)為0,則說明該對象沒有被引用,可以回收。
優(yōu)點是垃圾回收比較及時,實時性比較高,只要對象計數(shù)器為 0,則可以直接進(jìn)行回收操作;而缺點是無法解決循環(huán)引用的問題。
因為存在循環(huán)引用這個致命的硬傷,沒有一個主流JVM是采用引用計數(shù)法來實現(xiàn) GC 的,所以你現(xiàn)在完全忘記引用計數(shù)這種方式了。