JVM中的GC初識(shí)
GC簡(jiǎn)介
何為GC
GC(Garbage Collection)稱之為垃圾回收,是對(duì)內(nèi)存中的垃圾對(duì)象,采用一定的算法進(jìn)行內(nèi)存回收的一個(gè)動(dòng)作。比方說(shuō),java中的垃圾回收會(huì)對(duì)內(nèi)存中的對(duì)象進(jìn)行遍歷,對(duì)存活的對(duì)象進(jìn)行標(biāo)記,其未標(biāo)記對(duì)象可認(rèn)為是垃圾對(duì)象,然后基于特定算法進(jìn)行回收。
為何要學(xué)習(xí)GC
深入理解GC的工作機(jī)制,可以幫你寫出更好的Java應(yīng)用,提高開(kāi)發(fā)效率,同時(shí)也是進(jìn)軍大規(guī)模應(yīng)用開(kāi)發(fā)的一個(gè)前提。
GC垃圾對(duì)象判定
引用計(jì)數(shù)法
這個(gè)算法是給每一個(gè)對(duì)象設(shè)置一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用這個(gè)對(duì)象的時(shí)候,計(jì)數(shù)器就加 1,與之相反,每當(dāng)引用失效的時(shí)候就減 1。也就是以計(jì)數(shù)來(lái)判斷對(duì)象是否為垃圾。當(dāng)某個(gè)對(duì)象的引用計(jì)數(shù)器的值為0時(shí),表示這個(gè)對(duì)象不會(huì)在被實(shí)用,JVM中的GC被觸發(fā)時(shí),可回收這個(gè)對(duì)象。如圖所示:
其中:
- 綠色云朵是內(nèi)存中的根對(duì)象,表示程序中正在使用的對(duì)象。
- 藍(lán)色圓圈是內(nèi)存中的活動(dòng)對(duì)象,其中的數(shù)字表示其引用計(jì)數(shù)。
- 灰色圓圈是內(nèi)存中沒(méi)有活動(dòng)對(duì)象引用的對(duì)象,表示非活動(dòng)對(duì)象。
對(duì)于引用計(jì)數(shù)法,實(shí)現(xiàn)簡(jiǎn)單,垃圾對(duì)應(yīng)也便于識(shí)別。但也有一些缺陷,我們每個(gè)對(duì)象都需要有一個(gè)單獨(dú)的對(duì)象引用計(jì)數(shù)器,這個(gè)計(jì)數(shù)器的值還要經(jīng)常更新,還有就是有一個(gè)最嚴(yán)重的循環(huán)引用問(wèn)題,如圖所示:
其中紅色對(duì)象實(shí)際上是應(yīng)用程序不使用的垃圾。但由于引用計(jì)數(shù)的限制,仍然存在內(nèi)存泄漏。當(dāng)然也有一些辦法來(lái)應(yīng)來(lái)對(duì)這種情況, 例如 “弱引用”(‘weak’ references)或者使用其它的算法來(lái)排查循環(huán)引用等。
可達(dá)性分析法
這個(gè)算法的核心思路就是通過(guò)一系列的“GC Roots”對(duì)象作為起始點(diǎn),從這些對(duì)象開(kāi)始往下搜索,搜索所經(jīng)過(guò)的路徑稱之為“引用鏈”。當(dāng)一個(gè)對(duì)象到 GC Roots 沒(méi)有任何引用鏈相連的時(shí)候,證明此對(duì)象是可以被回收的。否則,證明這個(gè)對(duì)象有用,不是垃圾。如圖所示:
在GC遍歷(traverses)內(nèi)存中整體的對(duì)象關(guān)系圖(object graph)時(shí),首先要確定根對(duì)象,那什么樣的對(duì)象可作為根對(duì)象呢?GC規(guī)范中指出根對(duì)象可以是:
1)Java 虛擬機(jī)棧中的引用對(duì)象;
2)本地方法棧中 JNI(既一般說(shuō)的 Native 方法)引用的對(duì)象;
3)方法區(qū)中類靜態(tài)常量的引用對(duì)象;
4)方法區(qū)中常量的引用對(duì)象。
當(dāng)確定了根對(duì)象以后,進(jìn)而從根對(duì)象開(kāi)始進(jìn)行依賴查找,所有可訪問(wèn)到的對(duì)象都認(rèn)為是存活對(duì)象,然后進(jìn)行標(biāo)記(mark)。
說(shuō)明:標(biāo)記可達(dá)對(duì)象需要暫停所有應(yīng)用線程, 以確定對(duì)象的引用關(guān)系。其暫停的時(shí)間, 與堆內(nèi)存大小、對(duì)象的總數(shù)沒(méi)有直接關(guān)系, 而是由存活對(duì)象(alive objects)的數(shù)量來(lái)決定。
常見(jiàn)GC算法分析
標(biāo)記清除
標(biāo)記清除(Mark-Sweep)算法分為“標(biāo)記”和“清除”階段,它首先會(huì)標(biāo)記出內(nèi)存中所有不需要回收的對(duì)象,然后從內(nèi)存中清除所有未標(biāo)記的對(duì)象。 如圖所示:
標(biāo)記清除算法的的優(yōu)點(diǎn)是簡(jiǎn)單直接,缺點(diǎn)是效率低,并且可能會(huì)產(chǎn)生大量不連續(xù)的碎片。說(shuō)它效率低是因?yàn)闃?biāo)記和清除兩個(gè)過(guò)程都需要掃描內(nèi)存空間(第一次:標(biāo)記存活對(duì)象,第二次:清除沒(méi)有標(biāo)記的對(duì)象)。還有就是,清除后產(chǎn)生的大量不連續(xù)的內(nèi)存碎片空間,無(wú)法滿足較大對(duì)象的存儲(chǔ)需求,這樣就可能會(huì)再次觸發(fā)垃圾回收。所以此垃圾回收算法,應(yīng)該適合對(duì)象存活率較高的的內(nèi)存區(qū)域(比方說(shuō)JVM中的老年代)。
標(biāo)記復(fù)制
標(biāo)記復(fù)制(Mark-Copy)算法是將內(nèi)存分為大小相同的兩塊,當(dāng)這一塊使用完了,就把當(dāng)前存活的對(duì)象復(fù)制到另一塊,然后一次性清空當(dāng)前區(qū)塊。如圖所示:
“標(biāo)記-復(fù)制”算法的缺點(diǎn)顯而易見(jiàn),就是內(nèi)存空間利用率低,適用于那些對(duì)象生命周期短、回收頻率高的內(nèi)存區(qū)域(比方說(shuō)JVM中的年輕代)。
標(biāo)記整理
標(biāo)記整理清除(Mark-Sweep-Compact)算法結(jié)合了“標(biāo)記-清除”和“復(fù)制”兩個(gè)算法的優(yōu)點(diǎn)。第一階段從根節(jié)點(diǎn)開(kāi)始標(biāo)記所有被引用對(duì)象,第二階段遍歷整個(gè)堆,把存活對(duì)象“壓縮”復(fù)制到堆的其中一塊空間中,按順序排放。第三階段清理掉存活邊界以外的全部?jī)?nèi)存空間。如圖所示:
系統(tǒng)GC時(shí)每次執(zhí)行清除(sweeping)操作, JVM 都必須保證“不可達(dá)對(duì)象“占用的內(nèi)存能被回收然后重用。內(nèi)存是被回收了,但這有可能會(huì)產(chǎn)生大量的內(nèi)存碎片(類似于磁盤碎片), 進(jìn)而引發(fā)兩個(gè)問(wèn)題:
- 對(duì)象創(chuàng)建時(shí),執(zhí)行寫入操作越來(lái)越耗時(shí), 因?yàn)閷ふ乙粔K足夠大的空閑內(nèi)存會(huì)變得更加麻煩。
- 對(duì)象創(chuàng)建時(shí), JVM需要在連續(xù)的內(nèi)存塊中為對(duì)象分配內(nèi)存。如果碎片問(wèn)題很嚴(yán)重, 直至沒(méi)有空閑片段能存放新創(chuàng)建的對(duì)象,就會(huì)發(fā)生內(nèi)存分配錯(cuò)誤(allocation error)。
為了解決碎片問(wèn)題,JVM在啟動(dòng)GC執(zhí)行垃圾收集的過(guò)程中, 不僅僅是標(biāo)記和清除, 還需要執(zhí)行 “內(nèi)存碎片整理”。這個(gè)過(guò)程會(huì)讓所有可達(dá)對(duì)象(reachable objects)進(jìn)行依次移動(dòng),進(jìn)而可以消除(或減少)內(nèi)存碎片,并為新對(duì)象提供更大并且連續(xù)的內(nèi)存空間。
標(biāo)記整理算法避免了“標(biāo)記-清除”的碎片問(wèn)題,同時(shí)也避免了“復(fù)制”算法的空間問(wèn)題,由于需要向一側(cè)移動(dòng)等一系列操作,其效率相對(duì)低一些,但對(duì)內(nèi)存空間管理上十分優(yōu)異。適用于那些生命周期長(zhǎng)、回收頻率低,但注重回收一次內(nèi)存空間得到足夠釋放的場(chǎng)景。
分代回收
我們知道垃圾收集要停止整個(gè)應(yīng)用程序的運(yùn)行,那么假如這個(gè)收集過(guò)程需要的時(shí)間很長(zhǎng),就會(huì)對(duì)應(yīng)用程序產(chǎn)生很大性能問(wèn)題,如何解決這個(gè)問(wèn)題呢?通過(guò)實(shí)驗(yàn)發(fā)現(xiàn)內(nèi)存中的對(duì)象通常可以將其分為兩大類:
- 存活時(shí)間較短(這樣的對(duì)象比較多)。
- 存活時(shí)間較長(zhǎng)(這樣的對(duì)象比較少)。
基于對(duì)如上問(wèn)題的分析,科學(xué)家提出了分代回收思路,將VM中內(nèi)存分為年輕代(Young Generation)和老年代(Old Generation-老年代有時(shí)候也稱為年老區(qū)(Tenured)。例如:
Young區(qū)存儲(chǔ)的就是那些生命周期短,使用一兩次就不再使用的對(duì)象,回收一次基本上該區(qū)域十之有八的對(duì)象全部被回收清理掉,因此Young區(qū)采用的垃圾回收算法也就是“標(biāo)記-復(fù)制”算法。Old區(qū)存儲(chǔ)的是那些生命周期長(zhǎng),經(jīng)過(guò)多次回收后仍然存活的對(duì)象,就把它們放到Old區(qū)中,Old區(qū)一般不去判斷這些對(duì)象的可達(dá)性,直到Old區(qū)不夠用為止,再進(jìn)行一次統(tǒng)一的回收,釋放出足夠的連續(xù)的內(nèi)存空間。所以我們選擇“標(biāo)記-清除”或“標(biāo)記-整理”算法進(jìn)行垃圾收集。
在分代回收過(guò)程中,垃圾收集事件(Garbage Collection events)通常分為:
- Minor GC (小型GC):年輕代GC事件,(新對(duì)象)分配頻率越高, Minor GC 的頻率就越高。
- Major GC (大型GC): 老年代GC事件。
- Full GC (完全GC):整個(gè)堆的GC事件。
說(shuō)明:一般情況下可以將Major GC與Full GC看成是同一種GC。
章節(jié)面試分析
1)何為GC?
2)為什么要GC?
3)如何判定內(nèi)存中的對(duì)象是否為垃圾對(duì)象?
4)常用垃圾回收算法有哪些?
到此這篇關(guān)于JVM中的GC初識(shí)的文章就介紹到這了,更多相關(guān)初識(shí)JVM中的GC內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Socket通信介紹及可能遇到的問(wèn)題解決
最近在學(xué)習(xí)Java中的Socket通信,所以下面這篇文章主要給大家介紹了關(guān)于Java Socket通信介紹及可能遇到問(wèn)題的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起看看吧。2017-10-10springboot中mybatis多數(shù)據(jù)源動(dòng)態(tài)切換實(shí)現(xiàn)
在開(kāi)發(fā)中,動(dòng)態(tài)數(shù)據(jù)源配置還是用的比較多的,比如在多數(shù)據(jù)源使用方面,又或者是在多個(gè)DB之間切換方面。這里給出一個(gè)動(dòng)態(tài)數(shù)據(jù)源的配置方案,感興趣的可以了解一下2021-07-07Java中關(guān)于Collections集合工具類的詳細(xì)介紹
Java提供了一個(gè)操作Set、List和Map等集合的工具類:Collections,該工具提供了大量方法對(duì)集合元素進(jìn)行排序、查詢和修改等操作,還提供了將集合對(duì)象設(shè)置為不可變、對(duì)集合對(duì)象實(shí)現(xiàn)同步控制等方法2021-09-09Java 中組合模型之對(duì)象結(jié)構(gòu)模式的詳解
這篇文章主要介紹了Java 中組合模型之對(duì)象結(jié)構(gòu)模式的詳解的相關(guān)資料,希望通過(guò)本文能幫助到大家理解應(yīng)用對(duì)象結(jié)構(gòu)模型,需要的朋友可以參考下2017-09-09SpringBoot參數(shù)驗(yàn)證10個(gè)技巧值得收藏
Spring Boot提供了內(nèi)置的驗(yàn)證注解,可以幫助簡(jiǎn)單、快速地對(duì)輸入字段進(jìn)行驗(yàn)證,例如檢查 null 或空字段、強(qiáng)制執(zhí)行長(zhǎng)度限制、使用正則表達(dá)式驗(yàn)證模式以及驗(yàn)證電子郵件地址,那么在Spring Boot應(yīng)用中如何做好參數(shù)校驗(yàn)工作呢,本文提供了10個(gè)小技巧感興趣的朋友一起看看吧2023-08-08Java Socket編程實(shí)例(四)- NIO TCP實(shí)踐
這篇文章主要講解Java Socket編程中NIO TCP的實(shí)例,希望能給大家做一個(gè)參考。2016-06-06詳解JDBC對(duì)Mysql utf8mb4字符集的處理
這篇文章主要介紹了詳解JDBC對(duì)Mysql utf8mb4字符集的處理,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11Java實(shí)現(xiàn)Web應(yīng)用中的定時(shí)任務(wù)(實(shí)例講解)
下面小編就為大家分享一篇Java實(shí)現(xiàn)Web 應(yīng)用中的定時(shí)任務(wù)的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-11-11springboot集成測(cè)試最小化依賴實(shí)踐示例
這篇文章主要為大家介紹了springboot集成測(cè)試最小化依賴實(shí)踐示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06