JVM的垃圾回收機(jī)制你了解嗎
一:回收堆內(nèi)存
1.如何判定對(duì)象已死(可達(dá)性分析算法)
當(dāng)前主流的商用程序語(yǔ)言的內(nèi)存管理子系統(tǒng),都是通過(guò)可達(dá)性分析算法來(lái)判定對(duì)象是否存活的。這個(gè)算法的基本思路就是通過(guò)一系列稱為“GC Roots”的根對(duì)象作為起始節(jié)點(diǎn)集,從這些節(jié)點(diǎn)開(kāi)始,根據(jù)引用關(guān)系向下搜索,搜索過(guò)程所走過(guò)的路徑稱為“引用鏈”,如果某個(gè)對(duì)象到GC Roots間沒(méi)有任何引用鏈相連,或者用圖論的話來(lái)說(shuō)就是從GC Roots到這個(gè)對(duì)象不可達(dá)時(shí),則證明此對(duì)象是不可能再被使用的。
在Java技術(shù)體系里面,固定可作為GC Roots的對(duì)象包括以下幾種:
- 在虛擬機(jī)棧中引用的對(duì)象,譬如各個(gè)線程被調(diào)用的方法堆棧中使用到的參數(shù)、局部變量、臨時(shí)變量等;
- 在方法區(qū)中類靜態(tài)屬性引用的對(duì)象,譬如Java類的引用類型靜態(tài)變量;
- 在方法區(qū)中常量引用的對(duì)象,譬如字符串常量池里的引用;
- 在本地方法棧中引用的對(duì)象;
- JVM內(nèi)部的引用,如基本數(shù)據(jù)類型對(duì)應(yīng)的Class對(duì)象,常駐的異常對(duì)象(如NullPointExcepiton),以及系統(tǒng)類加載器;
- 所有被同步鎖(synchronized關(guān)鍵字)持有的對(duì)象;
- 反映Java虛擬機(jī)內(nèi)部情況的JMXBean、JVMTI中注冊(cè)的回調(diào)、本地代碼緩存等。
2.對(duì)象的引用級(jí)別
強(qiáng)引用:強(qiáng)引用是最傳統(tǒng)的“引用”的定義,是指在程序代碼之中普遍存在的引用賦值,即類似“Object obj=new Object()”這種引用關(guān)系。無(wú)論任何情況下,只要強(qiáng)引用關(guān)系還存在,垃圾收集器就永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象。
軟引用:軟引用是用來(lái)描述一些還有用,但非必須的對(duì)象。只被軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常前,會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行第二次回收,如果這次回收還沒(méi)有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常。
弱引用:弱引用也是用來(lái)描述那些非必須的對(duì)象,但是它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生為止。當(dāng)垃圾收集器開(kāi)始工作,無(wú)論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象。
虛引用:虛引用是最弱的一種引用關(guān)系。一個(gè)對(duì)象是否有虛引用的存在,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無(wú)法通過(guò)虛引用來(lái)取得一個(gè)對(duì)象實(shí)例。為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的只是為了能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。
3.對(duì)象的死亡過(guò)程
真正宣告一個(gè)對(duì)象死亡,至少要經(jīng)歷兩次標(biāo)記過(guò)程:
1.第一次標(biāo)記:如果對(duì)象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒(méi)有與GC Roots相連接的引用鏈,那它將會(huì)被第一次標(biāo)記,隨后進(jìn)行一次篩選,篩選的條件是此對(duì)象是否有必要執(zhí)行finalize()方法。假如對(duì)象沒(méi)有覆蓋finalize()方法,或者finalize()方法已經(jīng)被虛擬機(jī)調(diào)用過(guò),那么虛擬機(jī)將這兩種情況都視為“沒(méi)有必要執(zhí)行”。反之,該對(duì)象將會(huì)被放置在一個(gè)名為F-Queue的隊(duì)列之中,并在稍后由一條由虛擬機(jī)自動(dòng)建立的、低調(diào)度優(yōu)先級(jí)的Finalizer線程去執(zhí)行它們的==finalize()==方法。
2.第二次標(biāo)記:稍后,收集器將對(duì)F-Queue中的對(duì)象進(jìn)行第二次小規(guī)模的標(biāo)記。如果對(duì)象要在==finalize()==中成功拯救自己,只要重新與引用鏈上的任何一個(gè)對(duì)象建立關(guān)聯(lián)即可,譬如把自己(this)賦值給某個(gè)類變量或者對(duì)象的成員變量,那在第二次標(biāo)記時(shí)它將被移出“即將回收”的集合。如果對(duì)象這時(shí)候還沒(méi)有逃脫,那基本上它就真的要被回收了。
二:垃圾回收算法
分代收集理論
依據(jù)分代假說(shuō)理論,垃圾回收可以分為如下幾類:
1.新生代收集(Minor GC/Young GC):目標(biāo)為新生代的垃圾收集。
2.老年代收集(Major GC/Old GC):目標(biāo)為老年代的垃圾收集,目前只有CMS收集器會(huì)有這種行為。
3.混合收集(Mixed GC):目標(biāo)為整個(gè)新生代及部分老年代的垃圾收集,目前只有G1收集器會(huì)有這種行為。
4.整堆收集(Full GC):目標(biāo)為整個(gè)堆和方法區(qū)的垃圾收集。
1.標(biāo)記清除算法
算法分為“標(biāo)記”和“清除”兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后,統(tǒng)一回收掉所有被標(biāo)記的對(duì)象,也可以反過(guò)來(lái),標(biāo)記存活的對(duì)象,統(tǒng)一回收所有未被標(biāo)記的對(duì)象。它的主要缺點(diǎn)有兩個(gè):
(1)執(zhí)行效率不穩(wěn)定,進(jìn)行大量標(biāo)記和清除的動(dòng)作
(2)內(nèi)存空間碎片化
2.標(biāo)記復(fù)制算法
將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另外一塊上面,然后再把已使用過(guò)的內(nèi)存空間一次清理掉。對(duì)于多數(shù)對(duì)象都是可回收的情況,算法需要復(fù)制的就是占少數(shù)的存活對(duì)象,而且每次都是針對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,分配內(nèi)存時(shí)也就不用考慮有空間碎片的復(fù)雜情況,只要移動(dòng)堆頂指針,按順序分配即可。
這種復(fù)制回收算法的代價(jià)是將可用內(nèi)存縮小為了原來(lái)的一半,空間浪費(fèi)未免太多了一點(diǎn)。另外,如果內(nèi)存中多數(shù)對(duì)象都是存活的,這種算法將會(huì)產(chǎn)生大量的內(nèi)存間復(fù)制的開(kāi)銷。所以,現(xiàn)在的商用Java虛擬機(jī)大多都優(yōu)先采用了這種收集算法去回收新生代。
改進(jìn):
IBM公司曾有一項(xiàng)專門(mén)研究對(duì)新生代“朝生夕滅”的特點(diǎn)做了更量化的詮釋——新生代中的對(duì)象有98%熬不過(guò)第一輪收集。因此并不需要按照1∶1的比例來(lái)劃分新生代的內(nèi)存空間。在1989年,Andrew Appel針對(duì)具備“朝生夕滅”特點(diǎn)的對(duì)象,提出了一種更優(yōu)化的半?yún)^(qū)復(fù)制分代策略,現(xiàn)在稱為“Appel式回收”。
Appel式回收的具體做法是把新生代分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次分配內(nèi)存只使用Eden和其中一塊Survivor。發(fā)生垃圾搜集時(shí),將Eden和Survivor中仍然存活的對(duì)象一次性復(fù)制到另外一塊Survivor空間上,然后直接清理掉Eden和已用過(guò)的那塊Survivor空間。
HotSpot虛擬機(jī)的Serial、ParNew等新生代收集器均采用了這種策略來(lái)設(shè)計(jì)新生代的內(nèi)存布局。
HotSpot虛擬機(jī)默認(rèn)Eden和Survivor的大小比例是8:1:1,也即每次新生代中可用內(nèi)存空間為整個(gè)新生代容量的90%(Eden的80%加上一個(gè)Survivor的10%),只有一個(gè)Survivor空間,即10%的新生代是會(huì)被“浪費(fèi)”的。
98%的對(duì)象可被回收僅僅是“普通場(chǎng)景”下測(cè)得的數(shù)據(jù),任何人都沒(méi)有辦法百分百保證每次回收都只有不多于10%的對(duì)象存活,因此Appel式回收還有一個(gè)充當(dāng)罕見(jiàn)情況的“逃生門(mén)”的安全設(shè)計(jì),當(dāng)Survivor空間不足以容納一次Minor GC之后存活的對(duì)象時(shí),就需要依賴其他內(nèi)存區(qū)域(大多就是老年代)進(jìn)行分配擔(dān)保。
3.標(biāo)記整理算法
其中的標(biāo)記過(guò)程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向內(nèi)存空間一端移動(dòng),然后直接清理掉邊界以外的內(nèi)存。
如果移動(dòng)存活對(duì)象,尤其是在老年代這種每次回收都有大量對(duì)象存活區(qū)域,移動(dòng)存活對(duì)象并更新所有引用這些對(duì)象的地方將會(huì)是一種極為負(fù)重的操作,而且這種對(duì)象移動(dòng)操作必須全程暫停用戶應(yīng)用程序才能進(jìn)行,像這樣的停頓被最初的虛擬機(jī)設(shè)計(jì)者形象地描述為“Stop The World”。
三:垃圾收集器
1.G1(Garbage First)
在G1收集器出現(xiàn)之前的所有其他收集器,垃圾收集的目標(biāo)范圍要么是整個(gè)新生代(Minor GC),要么就是整個(gè)老年代(Major GC),再要么就是整個(gè)Java堆(Full GC)。而G1跳出了這個(gè)限制,它可以面向堆內(nèi)存任何部分來(lái)組成回收集進(jìn)行回收,衡量標(biāo)準(zhǔn)不再是它屬于哪個(gè)分代,而是哪塊內(nèi)存中存放的垃圾數(shù)量最多,回收收益最大,這就是G1收集器的Mixed GC模式。
G1將Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region),JVM目標(biāo)是不超過(guò)2048個(gè)Region(JVM源碼里TARGET_REGION_NUMBER 定義),實(shí)際可以超過(guò)該值,但是不推薦。一般Region大小等于堆大小除以2048,比如堆大小為4096M,則Region大小為2M,當(dāng)然也可以用參數(shù) “-XX:G1HeapRegionSize” 手動(dòng)指定Region大小,但是推薦默認(rèn)的計(jì)算方式。
G1保留了年輕代和老年代的概念,但不再是物理隔閡了,它們都是(可以不連續(xù))Region的集合。每一個(gè)Region都可以根據(jù)需要,扮演新生代的Eden空間、Survivor空間,或者老年代空間。還有一類專門(mén)用來(lái)存儲(chǔ)大對(duì)象的特殊區(qū)域(Humongous Region)
更具體的處理思路是,讓G1收集器去跟蹤各個(gè)Region里面的垃圾堆積的“價(jià)值”大小,價(jià)值即回收所獲得的空間大小以及回收所需時(shí)間的經(jīng)驗(yàn)值,然后在后臺(tái)維護(hù)一個(gè)優(yōu)先級(jí)列表,每次根據(jù)用戶設(shè)定允許的收集停頓時(shí)間(默認(rèn)是200毫秒),優(yōu)先處理回收價(jià)值收益最大的那些Region,這也就是“Garbage First”名字的由來(lái)。
一個(gè)Region可能之前是年輕代,如果Region進(jìn)行了垃圾回收,之后可能又會(huì)變成老年代,也就是說(shuō)Region的區(qū)域功能可能會(huì)動(dòng)態(tài)變化。
G1垃圾收集器對(duì)于對(duì)象什么時(shí)候會(huì)轉(zhuǎn)移到老年代跟之前講過(guò)的原則一樣,唯一不同的是對(duì)大對(duì)象的處理,G1有專門(mén)分配大對(duì)象的Region叫Humongous區(qū),而不是讓大對(duì)象直接進(jìn)入老年代的Region中。在G1中,大對(duì)象的判定規(guī)則就是一個(gè)大對(duì)象超過(guò)了一個(gè)Region大小的50%,比如按照上面算的,每個(gè)Region是2M,只要一個(gè)大對(duì)象超過(guò)了1M,就會(huì)被放入Humongous中,而且一個(gè)大對(duì)象如果太大,可能會(huì)橫跨多個(gè)Region來(lái)存放。
Humongous區(qū)專門(mén)存放短期巨型對(duì)象,不用直接進(jìn)老年代,可以節(jié)約老年代的空間,避免因?yàn)槔夏甏臻g不夠的GC開(kāi)銷。
Full GC的時(shí)候除了收集年輕代和老年代之外,也會(huì)將Humongous區(qū)一并回收。
G1收集器的運(yùn)作過(guò)程大致可劃分為以下四個(gè)步驟:
1.初始標(biāo)記:暫停所有的其他線程,并記錄下gc roots直接能引用的對(duì)象,速度很快
2.并發(fā)標(biāo)記:從GC Root開(kāi)始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析,遞歸掃描整個(gè)堆里的對(duì)象圖,找出要回收的對(duì)象,這階段耗時(shí)較長(zhǎng),但可與用戶程序并發(fā)執(zhí)行。
3.最終標(biāo)記:對(duì)用戶線程做另一個(gè)短暫的暫停,修正并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄。
4.篩選回收:負(fù)責(zé)更新Region的統(tǒng)計(jì)數(shù)據(jù),對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的停頓時(shí)間來(lái)制定回收計(jì)劃,可以自由選擇任意多個(gè)Region構(gòu)成回收集,然后把決定回收的那一部分Region的存活對(duì)象復(fù)制到空的Region中,再清理掉整個(gè)舊Region的全部空間。這里的操作涉及存活對(duì)象的移動(dòng),是必須暫停用戶線程,由多條收集器線程并行完成的。
G1從整體來(lái)看是基于標(biāo)記整理算法實(shí)現(xiàn)的收集器,但從局部上看又是基于標(biāo)記復(fù)制算法實(shí)現(xiàn)。無(wú)論如何,這兩種算法都意味著G1運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存空間碎片,垃圾收集完成之后能提供規(guī)整的可用內(nèi)存。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Spring Cloud超詳細(xì)i講解Feign自定義配置與使用
這篇文章主要介紹了SpringCloud Feign自定義配置與使用,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06關(guān)于實(shí)體類中Date屬性格式化@JsonFormat @DateTimeFormat
這篇文章主要介紹了關(guān)于實(shí)體類中Date屬性格式化@JsonFormat @DateTimeFormat問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07Spring中的StopWatch記錄操作時(shí)間代碼實(shí)例
這篇文章主要介紹了Spring中的StopWatch記錄操作時(shí)間代碼實(shí)例,spring-framework提供的一個(gè)StopWatch類可以做類似任務(wù)執(zhí)行時(shí)間控制,也就是封裝了一個(gè)對(duì)開(kāi)始時(shí)間,結(jié)束時(shí)間記錄操作的Java類,需要的朋友可以參考下2023-11-11spring?boot集成redisson的最佳實(shí)踐示例
這篇文章主要為大家介紹了spring?boot集成redisson的最佳實(shí)踐示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03基于java實(shí)現(xiàn)顏色拾色器并打包成exe
這篇文章主要為大家詳細(xì)介紹了如何基于java編寫(xiě)一個(gè)簡(jiǎn)單的顏色拾色器并打包成exe文件,文中的示例代碼講解詳細(xì),需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-10-10Java的Struts框架中的if/else標(biāo)簽使用詳解
這篇文章主要介紹了Java的Struts框架中的if/else標(biāo)簽使用詳解,Struts是Java的SSH三大web開(kāi)發(fā)框架之一,需要的朋友可以參考下2015-12-12