一篇文章帶你了解JVM垃圾回收
如何判斷對(duì)象是否死亡(兩種方法)。
簡(jiǎn)單的介紹一下強(qiáng)引用、軟引用、弱引用、虛引用(虛引用與軟引用和弱引用的區(qū)別、使用軟引用能帶來的好處)。
如何判斷一個(gè)常量是廢棄常量
如何判斷一個(gè)類是無用的類
垃圾收集有哪些算法,各自的特點(diǎn)?
HotSpot 為什么要分為新生代和老年代?
常見的垃圾回收器有哪些?
介紹一下 CMS,G1 收集器。
Minor Gc 和 Full GC 有什么不同呢?
1.堆空間的基本結(jié)構(gòu):
現(xiàn)在的垃圾回收器基本上都采用分代垃圾回收算法,可分為新生代和老年代,新生代又可分為Eden區(qū)、From Survivor0、To Survivor區(qū)。
對(duì)象首先在eden區(qū)域分配,再經(jīng)歷一次垃圾回收后,如果對(duì)象還存會(huì),就年齡加一,并進(jìn)入From Survive區(qū),當(dāng)年齡增加到一定程度(默認(rèn)15歲)時(shí),會(huì)進(jìn)入老年代。對(duì)象進(jìn)入老年代的年齡閾值可通過-Xx:MaxTenuringThreshold設(shè)置,這個(gè)值會(huì)在虛擬機(jī)運(yùn)行過程中進(jìn)行調(diào)整,HotSpot遍歷所有對(duì)象,按照年齡從小到大累計(jì)占用的區(qū)域,當(dāng)累計(jì)區(qū)域超過一半時(shí),取此時(shí)的年齡和設(shè)置的-Xx:MaxTenuringThreshold中較小值作為晉升老年代的年齡閾值。


針對(duì) HotSpot VM 的實(shí)現(xiàn),它里面的 GC 其實(shí)準(zhǔn)確分類只有兩大種:
部分收集 (Partial GC):
- 新生代收集(Minor GC / Young GC):只對(duì)新生代進(jìn)行垃圾收集;
- 老年代收集(Major GC / Old GC):只對(duì)老年代進(jìn)行垃圾收集。需要注意的是 Major GC 在有的語境中也用于指代整堆收集;
- 混合收集(MixedGC):對(duì)整個(gè)新生代和部分老年代進(jìn)行垃圾收集。
整堆收集 (Full GC):
- 收集整個(gè) Java 堆和方法區(qū)。
2.空間分配擔(dān)保機(jī)制
為了確保Major GC時(shí),老年代有足夠的空間容納新生代的所有對(duì)象。
《深入理解Java虛擬機(jī)》第三章對(duì)于空間分配擔(dān)保的描述如下:
JDK 6 Update 24 之前,在發(fā)生 Minor GC 之前,虛擬機(jī)必須先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,如果這個(gè)條件成立,那這一次 Minor GC可以確保是安全的。如果不成立,則虛擬機(jī)會(huì)先查看 -XX:HandlePromotionFailure參數(shù)的設(shè)置值是否允許擔(dān)保失敗(Handle Promotion Failure);如果允許,那會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于,將嘗試進(jìn)行一次 MinorGC,盡管這次 Minor GC 是有風(fēng)險(xiǎn)的;如果小于,或者 -XX: HandlePromotionFailure設(shè)置不允許冒險(xiǎn),那這時(shí)就要改為進(jìn)行一次 Full GC。
JDK 6 Update 24之后的規(guī)則變?yōu)橹灰夏甏倪B續(xù)空間大于新生代對(duì)象總大小或者歷次晉升的平均大小,就會(huì)進(jìn)行 MinorGC,否則將進(jìn)行 Full GC。
3.如何判斷一個(gè)對(duì)象已經(jīng)無效
引用計(jì)數(shù)法
給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它,計(jì)數(shù)器就加 1;當(dāng)引用失效,計(jì)數(shù)器就減 1;任何時(shí)候計(jì)數(shù)器為 0 的對(duì)象就是不可能再被使用的。
這個(gè)方法實(shí)現(xiàn)簡(jiǎn)單,效率高,但是目前主流的虛擬機(jī)中并沒有選擇這個(gè)算法來管理內(nèi)存,其最主要的原因是它很難解決對(duì)象之間相互循環(huán)引用的問題。
2.2 可達(dá)性分析算法
這個(gè)算法的基本思想就是通過一系列的稱為 “GC Roots” 的對(duì)象作為起點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,節(jié)點(diǎn)所走過的路徑稱為
引用鏈,當(dāng)一個(gè)對(duì)象到 GC Roots 沒有任何引用鏈相連的話,則證明此對(duì)象是不可用的。
可作為 GC Roots 的對(duì)象包括下面幾種:
- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象
- 本地方法棧(Native 方法)中引用的對(duì)象
- 方法區(qū)中類靜態(tài)屬性引用的對(duì)象
- 方法區(qū)中常量引用的對(duì)象
- 所有被同步鎖持有的對(duì)象
4 不可達(dá)的對(duì)象并非“非死不可”
即使在可達(dá)性分析法中不可達(dá)的對(duì)象,也并非是“非死不可”的,這時(shí)候它們暫時(shí)處于“緩刑階段”,要真正宣告一個(gè)對(duì)象死亡,至少要經(jīng)歷兩次標(biāo)記過程;可達(dá)性分析法中不可達(dá)的對(duì)象被第一次標(biāo)記并且進(jìn)行一次篩選,篩選的條件是此對(duì)象是否有必要執(zhí)行 finalize 方法。當(dāng)對(duì)象沒有覆蓋 finalize 方法,或 finalize 方法已經(jīng)被虛擬機(jī)調(diào)用過時(shí),虛擬機(jī)將這兩種情況視為沒有必要執(zhí)行。
被判定為需要執(zhí)行的對(duì)象將會(huì)被放在一個(gè)隊(duì)列中進(jìn)行第二次標(biāo)記,除非這個(gè)對(duì)象與引用鏈上的任何一個(gè)對(duì)象建立關(guān)聯(lián),否則就會(huì)被真的回收。
5 如何判斷一個(gè)常量是廢棄常量?
運(yùn)行時(shí)常量池主要回收的是廢棄的常量。那么,我們?nèi)绾闻袛嘁粋€(gè)常量是廢棄常量呢?
JDK1.7 之前運(yùn)行時(shí)常量池邏輯包含字符串常量池存放在方法區(qū), 此時(shí) hotspot 虛擬機(jī)對(duì)方法區(qū)的實(shí)現(xiàn)為永久代
JDK1.7字符串常量池被從方法區(qū)拿到了堆中, 這里沒有提到運(yùn)行時(shí)常量池,也就是說字符串常量池被單獨(dú)拿到堆,運(yùn)行時(shí)常量池剩下的東西還在方法區(qū), 也就是hotspot 中的永久代 。
JDK1.8 hotspot 移除了永久代用元空間(Metaspace)取而代之,這時(shí)候字符串常量池還在堆, 運(yùn)行時(shí)常量池還在方法區(qū), 只不過方法區(qū)的實(shí)現(xiàn)從永久代變成了元空間(Metaspace)
假如在字符串常量池中存在字符串 “abc”,如果當(dāng)前沒有任何 String 對(duì)象引用該字符串常量的話,就說明常量 “abc” 就是廢棄常量,如果這時(shí)發(fā)生內(nèi)存回收的話而且有必要的話,“abc” 就會(huì)被系統(tǒng)清理出常量池了。
6 如何判斷一個(gè)類是無用的類
方法區(qū)主要回收的是無用的類,那么如何判斷一個(gè)類是無用的類的呢?
判定一個(gè)常量是否是“廢棄常量”比較簡(jiǎn)單,而要判定一個(gè)類是否是“無用的類”的條件則相對(duì)苛刻許多。類需要同時(shí)滿足下面 3 個(gè)條件才能算是 “無用的類” :
- 該類所有的實(shí)例都已經(jīng)被回收,也就是 Java 堆中不存在該類的任何實(shí)例。
- 加載該類的ClassLoader 已經(jīng)被回收。 該類對(duì)應(yīng)的
- java.lang.Class 對(duì)象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
7.垃圾回收算法

7.1 標(biāo)記-清除算法
該算法分為“標(biāo)記”和“清除”階段:首先標(biāo)記出所有不需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收掉所有沒有被標(biāo)記的對(duì)象。
它是最基礎(chǔ)的收集算法,后續(xù)的算法都是對(duì)其不足進(jìn)行改進(jìn)得到。這種垃圾收集算法會(huì)帶來兩個(gè)明顯的問題:
效率問題
空間問題(標(biāo)記清除后會(huì)產(chǎn)生大量不連續(xù)的碎片)
7.2 標(biāo)記-復(fù)制算法
為了解決效率問題,“標(biāo)記-復(fù)制”收集算法出現(xiàn)了。它可以將內(nèi)存分為大小相同的兩塊,每次使用其中的一塊。當(dāng)這一塊的內(nèi)存使用完后,就將還存活的對(duì)象復(fù)制到另一塊去,然后再把使用的空間一次清理掉。這樣就使每次的內(nèi)存回收都是對(duì)內(nèi)存區(qū)間的一半進(jìn)行回收。
7.3 標(biāo)記-整理算法
根據(jù)老年代的特點(diǎn)提出的一種標(biāo)記算法,標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對(duì)可回收對(duì)象回收,而是讓所有存活的對(duì)象向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存。
7.4 分代收集算法
當(dāng)前虛擬機(jī)的垃圾收集都采用分代收集算法,這種算法沒有什么新的思想,只是根據(jù)對(duì)象存活周期的不同將內(nèi)存分為幾塊。一般將 java 堆分為新生代和老年代,這樣我們就可以根據(jù)各個(gè)年代的特點(diǎn)選擇合適的垃圾收集算法。
比如在新生代中,每次收集都會(huì)有大量對(duì)象死去,所以可以選擇”標(biāo)記-復(fù)制“算法,只需要付出少量對(duì)象的復(fù)制成本就可以完成每次垃圾收集。而老年代的對(duì)象存活幾率是比較高的,而且沒有額外的空間對(duì)它進(jìn)行分配擔(dān)保,所以我們必須選擇“標(biāo)記-清除”或“標(biāo)記-整理”算法進(jìn)行垃圾收集。
總結(jié)
這篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
使用lombok注解導(dǎo)致mybatis-plus TypeHandler失效的解決
這篇文章主要介紹了使用lombok注解導(dǎo)致mybatis-plus TypeHandler失效的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
springboot設(shè)置了server.port但是沒有用,還是8080問題
這篇文章主要介紹了springboot設(shè)置了server.port但是沒有用,還是8080問題的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
SpringCloud Feign 服務(wù)調(diào)用的實(shí)現(xiàn)
Feign是一個(gè)聲明性web服務(wù)客戶端。本文記錄多個(gè)服務(wù)之間使用Feign調(diào)用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01
使用Maven創(chuàng)建和管理多模塊項(xiàng)目的詳細(xì)步驟
使用Maven進(jìn)行多模塊項(xiàng)目管理是一種常見的做法,它可以幫助你組織大型項(xiàng)目,使其結(jié)構(gòu)更加清晰,便于維護(hù)和構(gòu)建,以下是使用Maven創(chuàng)建和管理多模塊項(xiàng)目的詳細(xì)步驟,需要的朋友可以參考下2024-10-10
Spring?Data?JPA?在?@Query?中使用投影的方法示例詳解
這篇文章主要介紹了Spring?Data?JPA?在?@Query?中使用投影的方法,大家需要注意如果要在 @Query 中使用投影,必須要主動(dòng)聲明要查詢的字段,并且主動(dòng)寫明字段的別名才行,本文通過sql代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-07-07

