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