JVM 垃圾回收算法 -可达性分析算法!!!高频面试!!!
前言:學習JVM,那么不可避免的要去了解JVM相關的垃圾回收算法,本文只是講了講了可達性分析算法,至于標記-清除、標記-復制,標記-整理,分代收集等等算法,會在近兩天的文章中陸續更新出來。
很喜歡一句話:“八小時內謀生活,八小時外謀發展。”
共勉
地點:湖南省永州市藍山縣舜河村
作者:用心笑*
JVM 垃圾回收算法 -可達性分析算法
- 一、先談談不被Java所用的引用計數法
- 二、可達性分析算法
- 2.1概念:
- 2.2、思路:
- 2.3、GC Roots可以是哪些?
- 1、詳細解釋:
- 2、總結
- 3、關鍵小技巧
- 4、注意
- 三、 對象的finalization機制
- 3.1、概述:
- 3.2、生存還是死亡?
- 3.3、具體過程
- 3.4、代碼演示
- 四、自言自語
一、先談談不被Java所用的引用計數法
在java中是通過引用來和對象進行關聯的,也就是說如果要操作對象,必須通過引用來進行。
那么很顯然一個簡單的辦法就是通過引用計數來判斷一個對象是否可以被回收。不失一般性,如果一個對象沒有任何引用與之關聯,則說明該對象基本不太可能在其他地方被使用到,那么這個對象就成為可被回收的對象了。這種方式成為引用計數法。
上面這段話看起來似乎并沒有什么問題,但是我想起Spring中那個循環依賴,然后一套在這個引用計數法身上就翻車了。如下圖:
為了解決這個問題,所以在Java中采取的是可達性分析法。即本文第二章節😁
二、可達性分析算法
2.1概念:
2.2、思路:
通過一系列稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索走過的路徑稱為“引用鏈”,當一個對象到 GC Roots 沒有任何的引用鏈相連時(從 GC Roots 到這個對象不可達)時,證明此對象不可用。以下圖為例:
再給大家舉個生活中實例: (我想這個舉例應該沒誰了吧)
Java 程序員 女朋友:new GirlFriend(); (🐕保命)🐱?🏍
2.3、GC Roots可以是哪些?
- 虛擬機棧中引用的對象
- 比如:各個線程被調用的方法中使用到的參數、局部變量等。
- 方法區中類靜態屬性引用的對象
- 比如:Java類的引用類型靜態變量
- 方法區中常量引用的對象
- 比如:字符串常量池(string Table)里的引用
- 本地方法棧內JNI(通常說的本地方法)引用的對象
- (可以理解為:引用Native方法的所有對象)
- 所有被同步鎖synchronized持有的對象
- Java虛擬機內部的引用。
- 基本數據類型對應的Class對象,一些常駐的異常對象(如:NullPointerException、OutOfMemoryError),系統類加載器。
- 反映java虛擬機內部情況的JMXBean、JVMTI中注冊的回調、本地代碼緩存等。
1、詳細解釋:
(1)首先第一種是虛擬機棧中的引用的對象,我們在程序中正常創建一個對象,對象會在堆上開辟一塊空間,同時會將這塊空間的地址作為引用保存到虛擬機棧中,如果對象生命周期結束了,那么引用就會從虛擬機棧中出棧,因此如果在虛擬機棧中有引用,就說明這個對象還是有用的,這種情況是最常見的。
(2)第二種是我們在類中定義了全局的靜態的對象,也就是使用了static關鍵字,由于虛擬機棧是線程私有的,所以這種對象的引用會保存在共有的方法區中,顯然將方法區中的靜態引用作為GC Roots是必須的。
(3)第三種便是常量引用,就是使用了static final關鍵字,由于這種引用初始化之后不會修改,所以方法區常量池里的引用的對象也應該作為GC Roots。
(4)第四種是在使用JNI技術時,有時候單純的Java代碼并不能滿足我們的需求,我們可能需要在Java中調用C或C++的代碼,因此會使用native方法,JVM內存中專門有一塊本地方法棧,用來保存這些對象的引用,所以本地方法棧中引用的對象也會被作為GC Roots。
2、總結
總結一句話就是,除了堆空間外的一些結構,比如 虛擬機棧、本地方法棧、方法區、字符串常量池 等地方對堆空間進行引用的,都可以作為GC Roots進行可達性分析。
除了這些固定的GC Roots集合以外,根據用戶所選用的垃圾收集器以及當前回收的內存區域不同,還可以有其他對象“臨時性”地加入,共同構成完整GC Roots集合。比如:分代收集和局部回收(PartialGC)。😶
3、關鍵小技巧
Root采用棧方式存放變量和指針,所以如果一個指針,它保存了堆內存里面的對象,但是自己又不存放在堆內存里面,那它就是一個Root。
4、注意
如果需要使用可達性算法來判斷內存是否可以回收,那么分析工作一定要在一個能保障一致性的快照中進行。否則分析結果的準確性就無法保證。這點也是導致GC進行時必須“stop The World”(即停止當前工作)的一個重要原因。
即使在可達性分析算法中不可達的對象,其實也并非是“非死不可”的,這時候它們暫時處于“緩刑”階段,要真正宣告一個對象死亡,至少要經歷再次標記過程。也就牽扯到下文的finalization機制啦
三、 對象的finalization機制
3.1、概述:
注意: 永遠不要主動調用某個對象的finalize()方法應該交給垃圾回收機制調用。理由包括下面三點:
- 在finalize()時可能會導致對象復活。
- finalize()方法的執行時間是沒有保障的,它完全由Gc線程決定,極端情況下,若不發生GC,則finalize()方法將沒有執行機會。
- 因為優先級比較低,即使主動調用該方法,也不會因此就直接進行回收
- 一個糟糕的finalize()會嚴重影響Gc的性能。(因為GC 期間會暫停用戶線程)🤭
由于finalize()方法的存在,虛擬機中的對象一般處于三種可能的狀態。
3.2、生存還是死亡?
如果從所有的根節點都無法訪問到某個對象,說明對象己經不再使用了。一般來說,此對象需要被回收。但事實上,也并非是“非死不可”的,這時候它們暫時處于“緩刑”階段。一個無法觸及的對象有可能在某一個條件下“復活”自己,如果這樣,那么對它的回收就是不合理的,為此,定義虛擬機中的對象可能的三種狀態。如下:
- 可觸及的:從根節點開始,可以到達這個對象。
- 可復活的:對象的所有引用都被釋放,但是對象有可能在finalize()中復活。
- 不可觸及的:對象的finalize()被調用,并且沒有復活,那么就會進入不可觸及狀態。不可觸及的對象不可能被復活,因為finalize()只會被調用一次。
以上3種狀態中,是由于finalize()方法的存在,進行的區分。只有在對象不可觸及時才可以被回收。
3.3、具體過程
即使在可達性分析算法中不可達的對象,其實也并非是“非死不可”的,這時候它們暫時處于“緩刑”階段,要真正宣告一個對象死亡,至少要經歷再次標記過程。😀
標記的前提是對象在進行可達性分析后發現沒有與GC Roots相連接的引用鏈。
判定一個對象objA是否可回收,至少要經歷兩次標記過程:
-
第一次標記🙄
- 如果對象objA到GC Roots沒有引用鏈,則進行第一次標記。
-
進行篩選,判斷此對象是否有必要執行finalize()方法
- 如果對象objA沒有重寫finalize()方法,或者finalize()方法已經被虛擬機調用過,則虛擬機視為“沒有必要執行”,objA被判定為不可觸及的。
- 如果對象objA重寫了finalize()方法,且還未執行過,那么objA會被插入到F-Queue隊列中,由一個虛擬機自動創建的、低優先級的Finalizer線程觸發其finalize()方法執行。
- finalize()方法是對象逃脫死亡的最后機會,稍后GC會對F-Queue隊列中的對象進行第二次標記。如果objA在finalize()方法中與引用鏈上的任何一個對象建立了聯系,那么在第二次標記時,objA會被移出“即將回收”集合。之后,對象會再次出現沒有引用存在的情況。在這個情況下,finalize方法不會被再次調用,對象會直接變成不可觸及的狀態,也就是說,一個對象的finalize方法只會被調用一次。
3.4、代碼演示
package com.crush.sringtable;/*** 測試Object類中finalize()方法* 對象復活場景** @author: crush* @create: 2021-08-04-09:06*/ public class CanReliveObj {/*** 類變量,屬于GC Roots的一部分*/public static CanReliveObj canReliveObj;@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("調用當前類重寫的finalize()方法");canReliveObj = this;}public static void main(String[] args) throws InterruptedException {canReliveObj = new CanReliveObj();canReliveObj = null;// 手動調用第一次GCSystem.gc();System.out.println("-----------------第一次gc操作------------");// 因為Finalizer線程的優先級比較低,暫停2秒,以等待它Thread.sleep(2000);if (canReliveObj == null) {System.out.println("obj is dead");} else {System.out.println("obj is still alive");}System.out.println("-----------------第二次gc操作------------");canReliveObj = null;System.gc();// 下面代碼和上面代碼是一樣的,但是 canReliveObj卻自救失敗了Thread.sleep(2000);if (canReliveObj == null) {System.out.println("obj is dead");} else {System.out.println("obj is still alive");}} } // 輸出: -----------------第一次gc操作------------ 調用當前類重寫的finalize()方法 obj is still alive -----------------第二次gc操作------------ obj is dead在進行第一次清除的時候,我們會執行finalize方法,然后 對象 進行了一次自救操作,但是因為finalize()方法只會被調用一次,因此第二次該對象將會被垃圾清除。
四、自言自語
慢慢的將學習形成一種習慣,讓自己更努力一些,多一些追求。
讓我們一起加油吧,我相信未來會因努力而改變的!!!
共勉🐱?🏍
總結
以上是生活随笔為你收集整理的JVM 垃圾回收算法 -可达性分析算法!!!高频面试!!!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 这次让我们从字节码文件来重新认识Stri
- 下一篇: JVM中垃圾回收相关算法 - 值得了解一