日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JVM_06 垃圾回收相关算法 [ 一 ]

發(fā)布時間:2025/3/15 编程问答 13 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM_06 垃圾回收相关算法 [ 一 ] 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言:

  • (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ù)法被淘汰的原因)

  • 代碼展示:

  • /*** -XX:+PrintGCDetails* 證明:java使用的不是引用計數(shù)算法*/ public class RefCountGC {//這個成員屬性唯一的作用就是占用一點內(nèi)存private byte[] bigSize = new byte[5 * 1024 * 1024];//5MBObject reference = null;public static void main(String[] args) {RefCountGC obj1 = new RefCountGC();RefCountGC obj2 = new RefCountGC();obj1.reference = obj2;obj2.reference = obj1;obj1 = null;obj2 = null;//顯式的執(zhí)行垃圾回收行為//這里發(fā)生GC,obj1和obj2能否被回收?System.gc();try {Thread.sleep(1000000);} catch (InterruptedException e) {e.printStackTrace();}} }

  • 注意:Java使用的不是引用計數(shù)法(Java之所以沒有使用引用計數(shù)法,是由于不能解決循環(huán)引用問題) | (Python使用了是引用計數(shù)法)

  • Python如何解決循環(huán)引用( 擴展了解 )

  • 手動解決:很好理解,就是在合適的時機,解除引用關系
  • 使用弱引用weakref,weakref是Python提供的標準庫,旨在解決循環(huán)引用(只要發(fā)生了回收,弱引用都會被回收)
  • 二、枚舉根節(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)勢:

  • 相對于引用計數(shù)法而言,可達性分析算法不僅同樣具備實現(xiàn)簡單和執(zhí)行高效等特點,更重要的是該算法可以有效解決在引用計數(shù)算法中循環(huán)引用的問題,防止內(nèi)存泄漏的發(fā)生
  • 相較于引用計數(shù)算法,這里的可達性分析就是Java、C#選擇的。這種類型的垃圾收集通常也叫做追蹤性垃圾收集
  • 可達性分析算法的 注意事項: 如果要使用可達性分析算法來判斷內(nèi)存是否可回收,那么分析工作必須在 一個能保障一致性的快照中進行。這點不滿足的話分析結果的準確性就無法保證。這點也是導致GC進行時必須“StopTheWorld"的一個重要原因。 (即使是號稱(幾乎)不會發(fā)生停頓的CMS收集器中,枚舉根節(jié)點時也是必須要停頓的)
  • 三、finalization機制

  • finalization機制說明
  • finalize( ) 方法允許在子類中被重寫,用于對象被回收時進行資源釋放。通常在這個方法中進行一些資源釋放和清理的工作,比如關閉文件、套接字和數(shù)據(jù)庫連接等(記住)

  • 當垃圾回收器發(fā)現(xiàn)沒有引用指向一個對象,即:垃圾收集此對象之前,總會先調(diào)用這個對象的finalize( )方法

  • Java語言提提供了對象終止(finalization)機制來允許開發(fā)人員提供對象被銷毀之前的自定義邏輯

  • 不主動調(diào)用某個對象的finalize( ) 方法
    永遠不要主動調(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)。如下:(掌握)

  • 可觸及的:從根節(jié)點開始,可以到達這個對象
  • 可復活的:對象的所有引用都被釋放,但是對象有可能在finalize( )中復活
  • 不可觸及的: 對象的finalize(
    )被調(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ù)量并不會太大,或者說非常低才行。因為新生代中的對象一般都是朝生夕死的,在新生代中使用復制算法是非常好的
  • 注意:是當伊甸園區(qū)滿后,會觸發(fā)minjor gc,進行垃圾的回收
  • 八、標記清除算法(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)

  • 背景:
  • 復制算法的高效性是建立在存活對象少、垃圾對象多的前提下的。這種情況在新生代經(jīng)常發(fā)生,但是在老年代,更常見的情況是大部分對象都是存活對象。如果依然使用復制算法,由于存活對象較多,復制的成本也將很高。因此,基于老年代垃圾回收的特性,需要使用其他的算法。
  • 標記一清除算法的確可以應用在老年代中,但是該算法不僅執(zhí)行效率低下,而且在執(zhí)行完內(nèi)存回收后還會產(chǎn)生內(nèi)存碎片,所以JVM的設計者需要在此基礎之上進行改進。標記一壓縮(Mark一Compact)算法由此誕生
  • 1970年前后,G. L. Steele 、C. J. Chene和D.S. Wise 等研究者發(fā)布標記一壓縮算法。在許多現(xiàn)代的垃圾收集器中,人們都使用了標記一壓縮算法或其改進版本。
  • 執(zhí)行過程:[掌握]
  • 第一階段和標記一清除算法一樣,從根節(jié)點開始標記所有被引用對象.
  • 第二階段將所有的存活對象壓縮到內(nèi)存的一端,按順序排放。
  • 最后,清理邊界外所有的空間。
  • 可以看到,標記的存活對象將會被整理,按照內(nèi)存地址依次排列,而未被標記的內(nèi)存會被清理掉。如此一來,當我們需要給新對象分配內(nèi)存時,JVM只需要持有一個內(nèi)存的起始地址即可,這比維護一個空閑列表顯然少了許多開銷。
  • 圖解:

  • 指針碰撞
    (如果內(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)

  • 新生代特點:區(qū)域相對老年代較小,對象生命周期短、存活率低,回收頻繁。
  • 這種情況復制算法的回收整理,速度是最快的。復制算法的效率只和當前存活對象大小有關,因此很適用于年輕代的回收。而復制算法內(nèi)存利用率不高的問題,通過hotspot中的兩個survivor的設計得到緩解
  • 老年代(Tenured Gen)
  • 老年代特點:區(qū)域較大,對象生命周期長、存活率高,回收不及年輕代頻繁。
  • 這種情況存在大量存活率高的對象,復制算法明顯變得不合適。一般是由標記一清除或者是標記一清除與標記一整理的混合實現(xiàn)。
  • 十一、增量收集算法(了解)

  • 上述現(xiàn)有的算法,在垃圾回收過程中,應用軟件將處于一種stop the World的狀態(tài)。在Stop the World狀態(tài)下,應用程序所有的線程都會掛起,暫停一切正常的工作,等待垃圾回收的完成。如果垃圾回收時間過長,應用程序會被掛起很久,將嚴重影響用戶體驗或者系統(tǒng)的穩(wěn)定性。為了解決這個問題,即對實時垃圾收集算法的研究直接導致了增量收集(Incremental Collecting) 算法的誕生
  • ②. 基本思想(理解)

    • 如果一次性將所有的垃圾進行處理,需要造成系統(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)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。