JVM_06 垃圾回收相关算法 [ 一 ]
前言:
- (1). 判斷對(duì)象存活的兩種方式(引用計(jì)數(shù)算法、枚舉根節(jié)點(diǎn)做可達(dá)性分析)
- (2).標(biāo)記階段(引用計(jì)數(shù)法、枚舉根節(jié)點(diǎn)做可達(dá)性分析)
- (3).清除階段(復(fù)制算法、標(biāo)記清除算法、標(biāo)記整理(壓縮)算法、分代收集、增量收集算法、分區(qū)算法)
一、 引用計(jì)數(shù)法
原理:假設(shè)有一個(gè)對(duì)象A,任何一個(gè)對(duì)象對(duì)A的引用,那么對(duì)象A的引用計(jì)數(shù)器+1,當(dāng)引用失敗時(shí),對(duì)象A的引用計(jì)數(shù)器就-1,如果對(duì)象A的計(jì)數(shù)器的值為0,就說(shuō)明對(duì)象A沒(méi)有引用了,可以被回收
最大的缺陷:無(wú)法解決循環(huán)引用的問(wèn)題,gc永遠(yuǎn)都清除不了(這也是引用計(jì)數(shù)法被淘汰的原因)
代碼展示:
注意:Java使用的不是引用計(jì)數(shù)法(Java之所以沒(méi)有使用引用計(jì)數(shù)法,是由于不能解決循環(huán)引用問(wèn)題) | (Python使用了是引用計(jì)數(shù)法)
Python如何解決循環(huán)引用( 擴(kuò)展了解 )
二、枚舉根節(jié)點(diǎn)做可達(dá)性分析
基本思路是通過(guò)一系列名為"GC Roots"的對(duì)象(集合)作為起點(diǎn),從這個(gè)被稱為GC ROOTs 的對(duì)象開(kāi)始向下搜索,如果一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連時(shí),則說(shuō)明此對(duì)象是不可達(dá)對(duì)象(被回收),否則就是可達(dá)對(duì)象 [掌握]
在java中,可作為GC Roots的對(duì)象有:[掌握]
- (1).虛擬機(jī)棧(棧幀中的本地變量表)中的引用對(duì)象(比如各個(gè)線程被調(diào)用的方法中使用到的參數(shù)、局部變量等)
- (2).本地方法棧中JNI(即一般來(lái)說(shuō)native方法)中引用的對(duì)象[ 線程中的start方法 ]
- (3).方法區(qū)中的靜態(tài)屬性引用的對(duì)象
- (4).方法區(qū)中常量引用的對(duì)象
- (5).所有被synchronized持有的對(duì)象
- (6).Java虛擬機(jī)內(nèi)部的引用(基本數(shù)據(jù)類型對(duì)應(yīng)的Class對(duì)象,一些常駐的異常對(duì)象 [如NullPointerException、OutofMemoryError],系統(tǒng)類加載器)
- (7).反映java虛擬機(jī)內(nèi)部情況的JMXBean、JVMTI中注冊(cè)的回調(diào)、本地代碼緩存等
- (8).注意:除了這些固定的GC
Roots集合之外,根據(jù)用戶所選用的垃圾收集器以及當(dāng)前回收的內(nèi)存區(qū)域 不同,還可以有其他對(duì)象臨時(shí)加入,共同構(gòu)架完成整GC Roots集合。比如: 分代收集和局部回收(面試加分項(xiàng))
關(guān)于GCroot對(duì)象集合注意事項(xiàng):
注意:除了這些固定的GC Roots集合之外,根據(jù)用戶所選用的垃圾收集器以及當(dāng)前回收的內(nèi)存區(qū)域不同,還可以有其他對(duì)象臨時(shí)加入,共同構(gòu)架完成整GC Roots 集合。比如: 分代收集和局部回收(面試加分項(xiàng))
解釋:如果只針對(duì)java堆中的某一區(qū)域進(jìn)行垃圾回收(比如: 典型的只針對(duì)新生代),必須考慮到內(nèi)存區(qū)域是虛擬機(jī)自己的實(shí)現(xiàn)細(xì)節(jié),更不是孤立封閉的,這個(gè)區(qū)域的對(duì)象完全有可能被其他區(qū)域的對(duì)象所引用時(shí)候就需要一并將關(guān)聯(lián)的區(qū)域?qū)ο笠布尤氲紾C Roots 集合中考慮,才能保證可達(dá)性分析的準(zhǔn)確性
小技巧:由于Root采用棧方式存放變量和指針,所以如果一個(gè)指針,它保存了堆內(nèi)存里面的對(duì)象,但是自己又不存放在堆內(nèi)存里面,那它就是一個(gè)Root
優(yōu)勢(shì):
三、finalization機(jī)制
finalize( ) 方法允許在子類中被重寫(xiě),用于對(duì)象被回收時(shí)進(jìn)行資源釋放。通常在這個(gè)方法中進(jìn)行一些資源釋放和清理的工作,比如關(guān)閉文件、套接字和數(shù)據(jù)庫(kù)連接等(記住)
當(dāng)垃圾回收器發(fā)現(xiàn)沒(méi)有引用指向一個(gè)對(duì)象,即:垃圾收集此對(duì)象之前,總會(huì)先調(diào)用這個(gè)對(duì)象的finalize( )方法
Java語(yǔ)言提提供了對(duì)象終止(finalization)機(jī)制來(lái)允許開(kāi)發(fā)人員提供對(duì)象被銷毀之前的自定義邏輯
永遠(yuǎn)不要主動(dòng)調(diào)用某個(gè)對(duì)象的finalize( ) 方法,應(yīng)該交給垃圾回收機(jī)制調(diào)用,理由包括下面三點(diǎn):
在finalize( )時(shí)可能會(huì)導(dǎo)致對(duì)象復(fù)活
finalize( )方法執(zhí)行時(shí)間是沒(méi)有保障的,它完全由GC線程決定,極端情況下,若不發(fā)生GC,則finalize( )
方法將沒(méi)有執(zhí)行機(jī)會(huì)
一個(gè)糟糕的finalize( )會(huì)嚴(yán)重影響GC的性能
由于finalize( )方法的存在,虛擬機(jī)中的對(duì)象一般處于三種可能的狀態(tài)
四、 finalize( )方法中虛擬機(jī)的狀態(tài)[掌握]
如果從所有的根節(jié)點(diǎn)都無(wú)法訪問(wèn)到某個(gè)對(duì)象,說(shuō)明對(duì)象已經(jīng)不再使用了。一般來(lái)說(shuō),此對(duì)象需要被回收,但事實(shí)上,也并非是"非死不可"的,這時(shí)候它們暫時(shí)處于"緩刑"階段。一個(gè)無(wú)法觸及的對(duì)象肯能在某一個(gè)條件下"復(fù)活"自己,如果這樣,那么對(duì)它的回收就是不合理的。為此,定義虛擬機(jī)中的對(duì)象可能有三種狀態(tài)。如下:(掌握)
)被調(diào)用,并且沒(méi)有復(fù)活,那么就會(huì)進(jìn)入不可觸及狀態(tài)。不可觸及的對(duì)象不可能被復(fù)活,因?yàn)閒inalize( )只會(huì)被調(diào)用一次
以上3種狀態(tài)中,是由于finalize( )方法的存在,進(jìn)行的區(qū)分。只有對(duì)象不可觸及才可以被回收
五、判斷一個(gè)對(duì)象是否可以進(jìn)行回收(理解)
以上3種狀態(tài)中,是由于finalize()方法的存在,進(jìn)行的區(qū)分。只有在對(duì)象不可觸及時(shí)才可以被回收。 判定是否可以回收具體過(guò)程 判定一個(gè)對(duì)象objA是否可回收,至少要經(jīng)歷兩次標(biāo)記過(guò)程:
六、代碼演示
/*** 測(cè)試Object類中finalize()方法,即對(duì)象的finalization機(jī)制。**/ public class CanReliveObj {public static CanReliveObj obj;//類變量,屬于 GC Root//此方法只能被調(diào)用一次@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("調(diào)用當(dāng)前類重寫(xiě)的finalize()方法");obj = this;//當(dāng)前待回收的對(duì)象在finalize()方法中與引用鏈上的一個(gè)對(duì)象obj建立了聯(lián)系}public static void main(String[] args) {try {obj = new CanReliveObj();// 對(duì)象第一次成功拯救自己obj = null;System.gc();//調(diào)用垃圾回收器System.out.println("第1次 gc");// 因?yàn)镕inalizer線程優(yōu)先級(jí)很低,暫停2秒,以等待它Thread.sleep(2000);if (obj == null) {System.out.println("obj is dead");} else {System.out.println("obj is still alive");}System.out.println("第2次 gc");// 下面這段代碼與上面的完全相同,但是這次自救卻失敗了obj = null;System.gc();// 因?yàn)镕inalizer線程優(yōu)先級(jí)很低,暫停2秒,以等待它Thread.sleep(2000);if (obj == null) {System.out.println("obj is dead");} else {System.out.println("obj is still alive");}} catch (InterruptedException e) {e.printStackTrace();}} }七、復(fù)制算法(Copying)
核心思想:將活著的內(nèi)存空間分為兩塊,每次只使用其中一塊,在垃圾回收時(shí)將正在.使用的內(nèi)存中的存活對(duì)象復(fù)制到未被使用的內(nèi)存塊中,之后清除正在使用的內(nèi)存塊中的所有對(duì)象,交換兩個(gè)內(nèi)存的角色,最后完成垃圾回收。
描述(重點(diǎn)掌握)
一般過(guò)程(圖解)
優(yōu)缺點(diǎn):掌握
- 優(yōu)點(diǎn):①.沒(méi)有標(biāo)記和清除過(guò)程,實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效 ②. 不會(huì)產(chǎn)生內(nèi)存碎片,且對(duì)象完整不丟
- 缺點(diǎn):①. 浪費(fèi)了10%的空間 ②.對(duì)于G1這種分拆成為大量region的GC,復(fù)制而不是移動(dòng),意味著GC需要維護(hù)region之間對(duì)象引用關(guān)系,不管是內(nèi)存占用或者時(shí)間開(kāi)銷也不小。
- 注意:復(fù)制算法需要復(fù)制的存活對(duì)象數(shù)量并不會(huì)太大,或者說(shuō)非常低才行。因?yàn)樾律械膶?duì)象一般都是朝生夕死的,在新生代中使用復(fù)制算法是非常好的
八、標(biāo)記清除算法(Mark一Sweep)
標(biāo)記一清除算法(Mark一Sweep)是一種非常基礎(chǔ)和常見(jiàn)的垃圾收集算法,該算法被J . McCarthy等人在1960年提出并并應(yīng)用于Lisp語(yǔ)言。
標(biāo)記: Collector(垃圾回收器)從引用根節(jié)點(diǎn)開(kāi)始遍歷,標(biāo)記所有被引用的對(duì)象。一般是在對(duì)象的Header中記錄為可達(dá)對(duì)象
清除: Collector(垃圾回收器)對(duì)堆內(nèi)存從頭到尾進(jìn)行線性的遍歷,如果發(fā)現(xiàn)某個(gè)對(duì)象在其Header中沒(méi)有標(biāo)記為可達(dá)對(duì)象,則將其回收。
圖解:
優(yōu)缺點(diǎn):[ 掌握 ]
- 優(yōu)點(diǎn):不需要額外的空間
- 缺點(diǎn):①.兩次掃描,耗時(shí)嚴(yán)重 ②.清理出來(lái)的空閑內(nèi)存不連續(xù),會(huì)產(chǎn)生內(nèi)存碎片,需要維護(hù)一個(gè)空閑列表③.效率低(經(jīng)歷了兩次遍歷)
九、標(biāo)記整理(壓縮)算法(Mark-Compact)
圖解:
指針碰撞
(如果內(nèi)存空間以規(guī)整和有序的方式分布,即已用和未用的內(nèi)存都各自一邊,彼此之間維系著一個(gè)記錄下一次分配起始點(diǎn)的標(biāo)記指針,當(dāng)為新對(duì)象分配內(nèi)存時(shí),只需要通過(guò)修改指針的偏移量將新對(duì)象分配在第一個(gè)空閑內(nèi)存位置上,這種分配方式就叫做指針碰撞(Bump the Pointer))
優(yōu)缺點(diǎn) [掌握]
- 優(yōu)點(diǎn):①. 消除了標(biāo)記一清除算法當(dāng)中,內(nèi)存區(qū)域分散的缺點(diǎn),我們需要給新對(duì)象分配內(nèi)存時(shí),JVM只 需要持有一個(gè)內(nèi)存的起始地址即可②.消除了復(fù)制算法當(dāng)中,內(nèi)存減半的高額代價(jià)
- 缺點(diǎn):①. 從效率.上來(lái)說(shuō),標(biāo)記一整理算法要低于復(fù)制算法。②.移動(dòng)對(duì)象的同時(shí),如果對(duì)象被其他對(duì)象引用,則還需要調(diào)整引用的地址。移動(dòng)過(guò)程中,需要全程暫停用戶應(yīng)用程序。即: STW
十、 分代收集
寫(xiě)在最前面:
(
分代算法是針對(duì)對(duì)象的不同特征,而使用合適的算法,這里面并沒(méi)有實(shí)際上的新算法產(chǎn)生。與其說(shuō)分代搜集算法是第五個(gè)算法,不如說(shuō)它是對(duì)前三個(gè)算法的實(shí)際應(yīng)用,在新生代使用復(fù)制算法eden在8分空間,survivor在兩個(gè)1分,只浪費(fèi)10%的空閑空間。老年代使用標(biāo)記清除/標(biāo)記壓縮算法清除)
沒(méi)有最好的算法,只有更合適的算法
分代算法是針對(duì)對(duì)象的不同特征,而使用合適的算法,這里面并沒(méi)有實(shí)際上的新算法產(chǎn)生。與其說(shuō)分代搜集算法是第五個(gè)算法,不如說(shuō)它是對(duì)前三個(gè)算法的實(shí)際應(yīng)用,在新生代使用復(fù)制算法eden在8分空間,survivor在兩個(gè)1分,只浪費(fèi)10%的空閑空間。老年代使用標(biāo)記清除/標(biāo)記壓縮算法清除
新生代(Young Gen)
十一、增量收集算法(了解)
②. 基本思想(理解)
- 如果一次性將所有的垃圾進(jìn)行處理,需要造成系統(tǒng)長(zhǎng)時(shí)間的停頓,那么就可以讓垃圾收集線程和應(yīng)用程序線程交替執(zhí)行。每次,垃圾收集線程只收集一小片區(qū)域的內(nèi)存空間,接著切換到應(yīng)用程序線程。依次反復(fù),直到垃圾收集完成
- 總的來(lái)說(shuō),增量收集算法的基礎(chǔ)仍是傳統(tǒng)的標(biāo)記一清除和復(fù)制算法。增量收集算法通過(guò)對(duì)線程間沖突的妥善處理,允許垃圾收集線程以分階段的方式完成標(biāo)記、清理或復(fù)制工作。
(使用這種方式,由于在垃圾回收過(guò)程中,間斷性地還執(zhí)行了應(yīng)用程序代碼,所以能減少系統(tǒng)的停頓時(shí)間。但是,因?yàn)榫€程切換和上下文轉(zhuǎn)換的消耗,會(huì)使得垃圾回收的總體成本上升,造成系統(tǒng)吞吐量的下降。)
十二、 分區(qū)算法(了解)
一般來(lái)說(shuō),在相同條件下,堆空間越大,一次GC時(shí)所需要的時(shí)間就越長(zhǎng),有關(guān)GC產(chǎn)生的停頓也越長(zhǎng)。為了更好地控制GC產(chǎn)生的停頓時(shí)間,將一塊大的內(nèi)存區(qū)域分割成多個(gè)小塊,根據(jù)目標(biāo)的停頓時(shí)間,每次合理地回收若干個(gè)小區(qū)間,而不是整個(gè)堆空間,從而減少一次GC所產(chǎn)生的停頓。
分代算法將按照對(duì)象的生命周期長(zhǎng)短劃分成兩個(gè)部分,分區(qū)算法將整個(gè)堆空間劃分成連續(xù)的不同小區(qū)間
每一個(gè)小區(qū)間都獨(dú)立使用,獨(dú)立回收。這種算法的好處是可以控制一次回收多少個(gè)小區(qū)間。
總結(jié)
以上是生活随笔為你收集整理的JVM_06 垃圾回收相关算法 [ 一 ]的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: JVM_05 执行引擎(Executio
- 下一篇: 面试题,你什么时候可以入职?回答不好,容