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