?? 嗨,您好 ?? 我是 vnjohn,在互聯(lián)網(wǎng)企業(yè)擔任 Java 開發(fā),CSDN 優(yōu)質(zhì)創(chuàng)作者
?? 推薦專欄:Spring、MySQL、Nacos、Java,后續(xù)其他專欄會持續(xù)優(yōu)化更新迭代
??文章所在專欄:JVM
?? 我當前正在學習微服務(wù)領(lǐng)域、云原生領(lǐng)域、消息中間件等架構(gòu)、原理知識
?? 向我詢問任何您想要的東西,ID:vnjohn
??覺得博主文章寫的還 OK,能夠幫助到您的,感謝三連支持博客??
?? 代詞: vnjohn
? 有趣的事實:音樂、跑步、電影、游戲
目錄
前言
在 Java 中,判定對象是否存活指的是哪些不再被程序所引用,也無法通過任何方式訪問的對象;具體來說,當一個對象不再被任何活動線程所引用,并且沒有被其他對象所引用時,它就被認為是 “死亡” 對象;“死亡” 對象占用內(nèi)存空間但不再有任何實際的用途,因此需要通過垃圾收集機制將其從內(nèi)存中釋放,以便重新利用內(nèi)存資源;Java 垃圾收集機制會自動識別、回收這些 “死亡” 對象,無需程序員手動管理內(nèi)存釋放的過程
什么是垃圾?
沒有引用指向或根對象不可達的引用對象,被稱之為垃圾
Java 與 C++ 對垃圾的處理方式有所不同,如下:
Java:自動回收垃圾,由 GC 回收機制去自動回收,開發(fā)效率高,執(zhí)行效率低
C++:手動處理垃圾,若忘記回收時,容易發(fā)生內(nèi)存泄漏,回收多次,會出現(xiàn)非法訪問問題,一般手動回收的代碼(delete)會寫在析構(gòu)函數(shù)中;開發(fā)效率低,執(zhí)行效率高
如何定位垃圾
在堆里面存放著 Java 世界中幾乎所有的對象實例,垃圾收集器在對堆進行回收前,第一件事情就是要確定這些對象哪些還 “存活” 著,哪些對象已經(jīng) “死亡” 了
引用計數(shù)算法
引用計數(shù)算法(Reference Counting):在對象中添加一個引用計數(shù)器,每當有一個其他對象引用它時,計數(shù)器值就加一;當引用失效時,計數(shù)器值就減一;任何時刻計數(shù)器為零的對象就是不可能再被使用的
引用指向一個對象,在它腦袋上寫一個數(shù)字,有幾個對象指向它就在它腦袋上寫一個幾,當這個數(shù)字變?yōu)?0 時,就說明沒有任何對象指向它,即為 “垃圾”
在 Java 領(lǐng)域中,至少主流的 Java 虛擬機都沒有選用引用計數(shù)算法來管理內(nèi)存,主要原因:一個看似很簡單的算法有很多例外情況要考慮,必須要配合大量額外處理才能正確地工作
譬如單純的引用計數(shù)算法就很難解決對象之間相互引用的問題,例如:A->B、B->A,A、B 計數(shù)器的值都為 1,所以在當前算法來說不是垃圾,但從此看來,沒有其他的引用會使用到它們,按理來說這幾個都應(yīng)該是為 “垃圾”
以上會出現(xiàn)的對象之間互相引用問題,通過代碼來演示,看 Java JVM 中是否使用到了引用計數(shù)算法來回收垃圾,如下:
/**
* @author vnjohn
* @since 2023/6/29
*/
public class ReferenceCountingGC {
public Object instance = null;
private byte[] bigSize = new byte[2 * 1024 * 1024];
public static void main(String[] args) {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
System.gc();
}
}
調(diào)整 JVM Options 參數(shù),增加打印 GC 回收詳情信息,如下:
# 打印 GC 回收時間、GC 回收詳情
-XX:+PrintGCTimeStamps -XX:+PrintGCDetails
控制臺打印結(jié)果如下:
0.107: [GC (System.gc()) [PSYoungGen: 6717K->608K(76288K)] 6717K->616K(251392K), 0.0044845 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
0.112: [Full GC (System.gc()) [PSYoungGen: 608K->0K(76288K)] [ParOldGen: 8K->378K(175104K)] 616K->378K(251392K), [Metaspace: 3100K->3100K(1056768K)], 0.0026966 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 76288K, used 3277K [0x000000076ab00000, 0x0000000770000000, 0x00000007c0000000)
eden space 65536K, 5% used [0x000000076ab00000,0x000000076ae334d8,0x000000076eb00000)
from space 10752K, 0% used [0x000000076eb00000,0x000000076eb00000,0x000000076f580000)
to space 10752K, 0% used [0x000000076f580000,0x000000076f580000,0x0000000770000000)
ParOldGen total 175104K, used 378K [0x00000006c0000000, 0x00000006cab00000, 0x000000076ab00000)
object space 175104K, 0% used [0x00000006c0000000,0x00000006c005e9e8,0x00000006cab00000)
Metaspace used 3130K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 343K, capacity 388K, committed 512K, reserved 1048576K
重點看前面兩行日志信息
年輕代:0.107: [GC (System.gc()) [PSYoungGen: 6717K(回收前大?。?>608K(回收后大?。?/mark>(76288K)] 6717K->616K(251392K), 0.0044845 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
老年代:0.112: [Full GC (System.gc()) [PSYoungGen: 608K(回收前大小)->0K(回收后大?。?/mark>(76288K)] [ParOldGen: 8K->378K(175104K)] 616K->378K(251392K), [Metaspace: 3100K->3100K(1056768K)], 0.0026966 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
以上標注黃色背景的日志內(nèi)容,可以看出 Java 虛擬機并沒有因為這兩個對象循環(huán)引用而放棄回收它們,這也從側(cè)面說明了 Java 虛擬機并不是通過引用計數(shù)算法來判斷對象是否存活的
JDK 8 默認使用的垃圾收集器:Parallel Scavenge、Parallel Old
Java中常見的垃圾收集器,如:Serial、Parallel、CMS、G1 等,并不使用引用計數(shù)算法,而是采用基于可達性分析的算法來進行垃圾回收,這個也是后面要講到的算法
可達性分析算法
可達性分析算法(Reachability Analysis):它是一個更廣泛的概念,它是一類以可達性作為判斷對象是否存活基礎(chǔ)的算法,除了根可達算法外,還有其他的可達性分析算法,如:可達性分析與復制算法、可達性分析與標記-清除算法等
根可達算法是可達性分析算法中的一種具體實現(xiàn)方式,根可達算法是從一組稱為 “GC Roots” 根對象開始,通過引用關(guān)系向下搜索,搜索過程所走過的過程稱為 “引用鏈”;若某個對象到 GC Roots 間沒有任何引用鏈相連或者用圖論的方式來說就是從 GC Roots 到這個對象不可達時,則證明此對象是不可能再被使用的
如上圖,在 Java 技術(shù)體系中,固定可作為 GC Roots 對象包括以下幾種:
- 線程棧變量:在虛擬機棧(棧幀中的本地變量表)中引用的對象,譬如各個線程被調(diào)用的方法堆棧中使用到的參數(shù)、局部變量、臨時變量等
比如:main 方法主線程開始運行,主線程棧中變量調(diào)用了其他的方法,主線程棧中的方法訪問到的對象叫根對象
- 靜態(tài)變量:在方法區(qū)類靜態(tài)屬性引用的對象,譬如 Java 類引用類型的靜態(tài)變量
靜態(tài)變量初始化,能夠訪問到的對象稱之為根對象
- 常量池:在方法區(qū)中常量引用的對象,譬如字符串常量池(String Constant Pool)里面的引用
若一個 class 能夠用到其他的 class 對象稱之為根對象
- JNI 指針:在本地方法棧中 JNI(Java Native Interface)方法引用的對象
- 基礎(chǔ)數(shù)據(jù)類型 Class 對象:Java 虛擬機內(nèi)部的引用,如 int 類型對應(yīng) Class 對象是 Integer.TYPE 或 int.class、long 類型對應(yīng) Class 對象是 Long.TYPE 或 long.class
- 常駐異常對象:Java 虛擬機內(nèi)部的引用,如 NullPointException、OutOfMemoryError
- 系統(tǒng)類加載器
- 同步鎖持有對象:被同步鎖 synchronized 持有的對象
GC Roots 是垃圾收集器判斷對象是否存活的起點,不同的垃圾收集器會根據(jù) GC Roots 選擇合適的垃圾回收算法來進行垃圾回收、內(nèi)存管理,它們的共同協(xié)作以確保內(nèi)存的有效利用和程序的正常執(zhí)行
常見的垃圾回收算法:復制(Copying)算法、標記-清除 (Mark-Sweep)算法、標記-整理(Mark-Compact)算法
至于很多人說還有分代算法,在我看來,分代模型應(yīng)該是最準確的說法,分代算法不是指具體的一種算法,而是一種垃圾回收的策略或模型
由于對象的生命周期大部分是朝生夕死的,只有少數(shù)對象是長期存活的,基于此,垃圾收集器將堆內(nèi)存劃分為不同的代,分代模型將堆內(nèi)存主要劃分為新生代(Young Generation)和老年代(Old Generation)
G1 垃圾收集器邏輯分代,物理不分代
ZGC、Shenandoah 垃圾收集器沒有物理分代,也沒有邏輯分代
其他的垃圾收集器一般要么作用于新生代要么作用于老年代,例如:Parallel Scavenge-新生代、Parallel Old-老年代
關(guān)于垃圾回收算法、垃圾收集器后續(xù)文章見分曉,這里不過多展開~
總結(jié)
該篇博文講解判定對象是否存活的條件通過什么方式去做的,引用計數(shù)器算法、根可達算法,在引用計數(shù)器算法中,通過簡單的案例來演示在 Java 程序中并未通過該算法來判定對象是否存活,而是通過根可達算法去作判別的,羅列了 Java GC Roots 不同的種類,簡要闡述了為下文作鋪墊的垃圾回收算法、垃圾收集器,希望能先帶你一起了解這方面的前置知識!
參考文獻:《深入理解 Java 虛擬機》周志明著
博文放在 JVM 專欄里,歡迎訂閱,會持續(xù)更新!
如果覺得博文不錯,關(guān)注我 vnjohn,后續(xù)會有更多實戰(zhàn)、源碼、架構(gòu)干貨分享!
推薦專欄:Spring、MySQL,訂閱一波不再迷路文章來源:http://www.zghlxwxcb.cn/news/detail-522473.html
大家的「關(guān)注?? + 點贊?? + 收藏?」就是我創(chuàng)作的最大動力!謝謝大家的支持,我們下文見!文章來源地址http://www.zghlxwxcb.cn/news/detail-522473.html
到了這里,關(guān)于引用計數(shù) vs 根可達算法:深入比較對象存活判定的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!