根可达算法的根_好屌好屌的「GC系列」JVM垃圾定位及垃圾回收算法浅析
0x01 什么是垃圾
很簡(jiǎn)單,沒(méi)有引用指向的任何對(duì)象都叫做垃圾(garbage)。
什么是garbage
在某一內(nèi)存空間中,Java程序制造了很多對(duì)象被引用,有的對(duì)象還引用別的對(duì)象,中途有對(duì)象不被需要了就沒(méi)有指向他的引用了,這些沒(méi)有引用指向的東西就是垃圾。
這些垃圾不需要自己回收,JVM中有類似于街道上那些勤勞的環(huán)衛(wèi)工的人,在幫忙回收垃圾。
0x02 如何找到垃圾
那么,幫忙回收垃圾的人是如何找到垃圾的呢?JVM中一般有兩種算法:
- Reference Count 引用計(jì)數(shù)
- Root Searching 根可達(dá)算法
2.1 引用計(jì)數(shù)
引用計(jì)數(shù)法設(shè)定給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值加1;當(dāng)引用失效時(shí),計(jì)數(shù)器值減1,引用數(shù)量為0的時(shí)候,則說(shuō)明對(duì)象沒(méi)有被任何引用指向,,可以認(rèn)定是“垃圾”對(duì)象。
引用計(jì)數(shù)法
但是引用計(jì)數(shù)法不能解決循環(huán)引用的問(wèn)題,比如O1->O2->O3->O1,當(dāng)沒(méi)有引用指向他們?nèi)魏我粋€(gè)的時(shí)候,他們的reference count都是1,按照引用計(jì)數(shù)法,他們都不是垃圾。
而事實(shí)上,沒(méi)有任何引用指向O1、O2、O3這一坨了,他們都是垃圾。
JVM(看向O1):堅(jiān)決不能容忍垃圾!
O1:看什么,我的引用計(jì)數(shù)為1,不是0,我不是垃圾。
JVM:不好意思,我不是針對(duì)你,我是說(shuō)你們一坨都是垃圾!
引用計(jì)數(shù)法無(wú)法確定垃圾的情況
2.2 根可達(dá)算法
引用計(jì)數(shù)法不能解決循環(huán)引用的問(wèn)題,可采用根可達(dá)算法(Root Searching)。
其算法思路就是通過(guò)一系列名為 GC Roots 的對(duì)象作為根,從根上開始向下搜索,搜索所走過(guò)的路徑稱為引用鏈(Reference Chain),當(dāng)一個(gè)對(duì)象到 GC Roots 沒(méi)有任何引用鏈相連時(shí),則證明此對(duì)象是不可用的。
那么哪些是 Roots 呢?
- 線程棧變量
Java程序從main方法開始執(zhí)行,main方法會(huì)開啟一個(gè)線程,這個(gè)線程里有線程棧,里面有棧幀。
從main開始這個(gè)線程棧幀里面的這些個(gè)叫做根對(duì)象。
- 靜態(tài)變量
一個(gè)class被load到內(nèi)存之后,馬上就對(duì)靜態(tài)變量進(jìn)行初始化,所以靜態(tài)變量訪問(wèn)到的對(duì)象也是根對(duì)象。
- 常量池
如果一個(gè)class能夠用到其他的class的對(duì)象,那么他就是根對(duì)象。
- JNI指針
本地方法用到本地的對(duì)象也是根對(duì)象。
總之,當(dāng)一個(gè)程序起來(lái)之后馬上需要的對(duì)象叫做根對(duì)象。
0x03 常見的垃圾回收算法
垃圾找到了之后就要回收,那么JVM怎么進(jìn)行垃圾回收呢?
如何進(jìn)行垃圾清除,常用的算法有3種:
- Mark-Sweep 標(biāo)記清除
- Coping 拷貝算法
- Mark-Compact 標(biāo)記壓縮
3.1 標(biāo)記清除
先標(biāo)記垃圾,然后清除垃圾。通過(guò)該算法,先找到那些有用的對(duì)象,沒(méi)有用的并出來(lái)然后把它們清除掉。
從圖中可以看出,標(biāo)記清除算法將垃圾標(biāo)記并清除后,內(nèi)存中原先不可用的內(nèi)存變成了空閑的可用的,但是這些內(nèi)存有些有些不連續(xù)了,也就是說(shuō)產(chǎn)生了碎片。
再一個(gè),如果存活的對(duì)象比較多,這種情況標(biāo)記清除算法的執(zhí)行效率比較高。相反執(zhí)行效率就稍微低一點(diǎn),因?yàn)樾枰獌杀閽呙?#xff0c;第一次掃描找到那些有用的,第二次掃描把那些沒(méi)用的找出來(lái)清理掉。
3.2 拷貝算法
拷貝算法,就是把內(nèi)存一分為二,比如A、B兩個(gè)區(qū)域,分開之后,把A區(qū)域中有用的拷貝到B區(qū)域,拷貝完成后,把A區(qū)域全部清除,下次再分配內(nèi)存的時(shí)候先往B區(qū)域分配,如此往復(fù)。
1) 把內(nèi)存一分為二,分為A、B兩個(gè)區(qū)域,分配內(nèi)存先往A區(qū)域分配
2) 拷貝A區(qū)域中存活的有用的對(duì)象到B區(qū)域
拷貝完成的狀態(tài):
3) 將A區(qū)域全部清除(內(nèi)存全部釋放)
4) 之后再分配內(nèi)存的時(shí)候往B區(qū)域分配
清除B之后再繼續(xù)往A區(qū)域分配,如此往復(fù),拷貝來(lái)拷貝去。
以上過(guò)程就是拷貝算法。
該算法適用于存活對(duì)象較少的情況,只掃描一次,效率有所提高并且沒(méi)有產(chǎn)生內(nèi)存碎片。需要注意的是,移動(dòng)復(fù)制對(duì)象時(shí)須調(diào)整對(duì)象引用。
缺點(diǎn)也顯而易見,得準(zhǔn)備兩份內(nèi)存,浪費(fèi)空間。
3.3 標(biāo)記壓縮
對(duì)象分配到內(nèi)存之后,需要回收的時(shí)候,先把沒(méi)有引用指向的對(duì)象標(biāo)記為垃圾,然后把后面存活的對(duì)象拷貝到標(biāo)記的那個(gè)地方,不僅如此,最后凡是有用的對(duì)象全部移到前面,無(wú)論這個(gè)內(nèi)存是沒(méi)有使用,都?jí)嚎s整理到前面,最后剩下的大塊空間就全部清理出來(lái)了。
可以看到,標(biāo)記壓縮進(jìn)行回收垃圾之后,空間連續(xù),沒(méi)有碎片。
我們分析一下該算法的實(shí)現(xiàn)思路,
1)通過(guò)GC Roots找到那些有用的不可回收的
2)把不可回收的有用對(duì)象往前移動(dòng)
所以肯定需要掃描兩次內(nèi)存,而且還要移動(dòng)對(duì)象,第一次掃描先找游泳對(duì)象,第二次掃描移動(dòng)對(duì)象。
移動(dòng)的過(guò)程中,如果是多線程還要考慮線程同步,所以標(biāo)記壓縮算法效率上要低一些。
該算法的優(yōu)點(diǎn)在于對(duì)象分配不會(huì)產(chǎn)生內(nèi)存減半,而且不會(huì)產(chǎn)生內(nèi)存碎片。
0x04 小結(jié)
作者:行百里er
鏈接:https://juejin.im/post/6888847017846669320
來(lái)源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
總結(jié)
以上是生活随笔為你收集整理的根可达算法的根_好屌好屌的「GC系列」JVM垃圾定位及垃圾回收算法浅析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 计算机程序的构造和解释 python_S
- 下一篇: java for each 原理_Jav