垃圾收集機(jī)制
在虛擬機(jī)內(nèi)存模型中:
- 程序計(jì)數(shù)器辐赞,消耗內(nèi)存可以忽略不計(jì)
- 虛擬機(jī)棧部翘,在編譯期可知需要分配多少內(nèi)存空間,棧幀入棧分配空間响委,出椥滤迹回收內(nèi)存窖梁。
- 本地方法棧,與虛擬機(jī)椉星簦基本一致纵刘。
- Java堆,最大的內(nèi)存區(qū)域荸哟,對象幾乎在運(yùn)行時才分配內(nèi)存假哎,創(chuàng)建頻繁甚至需要同步分配,所以堆內(nèi)存自動回收機(jī)制特別重要鞍历。
- 方法區(qū)舵抹,同樣需要內(nèi)存回收。
本章了解整個垃圾收集機(jī)制
- 了解垃圾收集流程
- 重點(diǎn)掌握虛擬機(jī)垃圾收集算法:
- 對象存活判定算法
- 垃圾收集算法
- 虛擬機(jī)GC算法實(shí)現(xiàn)
- 垃圾收集器
- 對象在GC時如何分配
對象存活判定算法
什么時候發(fā)生GC
當(dāng)分配Java對象內(nèi)存時劣砍,Java堆內(nèi)存空間不夠掏父,且無法擴(kuò)展時,進(jìn)行一次GC
GC處理的是Java堆中的對象秆剪,哪些對象是需要回收的呢
這就需要對象存活判定算法赊淑,目前有兩種對象存活判定算法
- 引用計(jì)數(shù)法
- 可達(dá)性分析算法
引用計(jì)數(shù)法是什么
對象中設(shè)置一個引用計(jì)數(shù)器,每當(dāng)有一個地方引用它時仅讽,計(jì)數(shù)器值加1陶缺。引用失效時,計(jì)數(shù)器值減1洁灵。
計(jì)數(shù)器值為0的對象就是可被回收的對象饱岸。
引用計(jì)數(shù)法有什么優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):實(shí)現(xiàn)簡單,判定效率高
- 缺點(diǎn):無法解決對象直接相互循環(huán)引用的問題
什么是對象間的相互循環(huán)引用
例如:對象A和對象B都有字段ref, 現(xiàn)在讓A.ref=B, B.ref=A
然后手動執(zhí)行GC, 雖然這兩個對象不可能再被訪問徽千,但他們之間的相互引用讓引用計(jì)數(shù)器都不等于0苫费,無法被回收。
java虛擬機(jī)使用的是引用計(jì)數(shù)法嗎
不是双抽,因?yàn)閷ο笾苯酉嗷パh(huán)引用的問題百框,java虛擬機(jī)使用的是可達(dá)性分析算法
什么是可達(dá)性分析算法
通過一系列的GC Roots
的對象作為起始點(diǎn),從這些起始點(diǎn)開始向下搜索牍汹,搜索走過的路徑被稱為引用鏈
當(dāng)一個對象到GC Roots沒有任何應(yīng)用鏈相連铐维,則證明對象是不可用的。
也可以說慎菲,從GC Roots到對象不可達(dá)時嫁蛇,則對象可回收。
一系列的GC Roots對象露该,GC Roots對象有哪些
- 虛擬機(jī)棧中引用的對象睬棚,即棧幀中本地變量表中對象
- 方法區(qū)中類靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
- 本地方法棧中JNI,即Native方法中引用的對象。
當(dāng)GC Roots到對象不可達(dá)時,對象就一定可以被回收嗎
當(dāng)GC Roots到對象不可達(dá)時抑党,對象不一定會被回收包警,需要經(jīng)歷兩次標(biāo)記。
- 第一次GC Roots 到對象不可達(dá)時新荤,對象被標(biāo)記1次揽趾。
- 判斷對象是否覆蓋了finalize()方法
- 對象覆蓋了finalize()方法,判斷虛擬機(jī)是否執(zhí)行了finalize方法
- 虛擬機(jī)沒有執(zhí)行finalize方法苛骨,將對象放入F-Queue隊(duì)列中篱瞎,待回收
- 稍后虛擬機(jī)自動建立低優(yōu)先級的Finalizer線程執(zhí)行F-Queue隊(duì)列中對象的finalize方法。
- finalize方法中對象與引用鏈上鏈接建立關(guān)聯(lián)痒芝,即GC Roots到對象可達(dá)
- finalize方法中對象沒有雨引用鏈上鏈接建立關(guān)聯(lián)
- 稍后GC對F-Queue隊(duì)列中對象進(jìn)行第二次標(biāo)記
- 與引用鏈建立鏈接的對象俐筋,移除待回收集合
- 沒有與引用鏈建立鏈接的對象,對象可回收
- 稍后虛擬機(jī)自動建立低優(yōu)先級的Finalizer線程執(zhí)行F-Queue隊(duì)列中對象的finalize方法。
- 虛擬機(jī)已經(jīng)執(zhí)行了finalize方法严衬,對象可回收
- 虛擬機(jī)沒有執(zhí)行finalize方法苛骨,將對象放入F-Queue隊(duì)列中篱瞎,待回收
- 對象沒有覆蓋finalize()方法澄者,對象可回收
- 對象覆蓋了finalize()方法,判斷虛擬機(jī)是否執(zhí)行了finalize方法
虛擬機(jī)自動建立的低優(yōu)先級的Finalizer線程執(zhí)行的時間很長怎么辦
Finalizer線程中對象finalize方法可能并不會等待方法執(zhí)行結(jié)束,因?yàn)閒inalize方法可能出現(xiàn)死循環(huán)等異常情況请琳,導(dǎo)致整個內(nèi)存回收機(jī)制崩潰粱挡。
所以只要執(zhí)行了finalize方法的對象,且沒有與引用鏈建立關(guān)聯(lián)俄精,對象就是可回收的询筏。雖然可能方法沒有結(jié)束。
對象的finalize方法有點(diǎn)像C++的析構(gòu)函數(shù)呀
是的竖慧,finalize方法就是Java對C++做出的妥協(xié)嫌套。
盡量不要使用這個方法,try-catch-finally可以做的更好圾旨。
方法區(qū)或永久代也可能被GC嗎
方法區(qū)或永久代也會被GC, 雖然效率會很低踱讨。
方法區(qū)或永久代會回收兩部分內(nèi)容:
- 廢棄常量
- 無用的類
這里的常量是指什么
這里是指運(yùn)行時常量池中常量:字面量和符號引用
常量如何判斷可回收
- 堆中沒有對象引用該字面量或符號引用
- 其他地方也沒有引用該字面量或符號引用
jdk1.8移除了永生代,會有什么變化嗎
常量被移到堆中砍的,即常量的回收與對象的回收一致了痹筛。
所以jdk1.8只負(fù)責(zé)類回收。
類如何判斷可回收
條件苛刻挨约,下面3個條件同時滿足才可以回收
- 該類所有實(shí)例被回收味混,即java堆中不存在該類的任何實(shí)例
- 加載該類的ClassLoader已經(jīng)被回收
- 該類對應(yīng)的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法诫惭。
而且是否對類進(jìn)行回收,虛擬機(jī)還提供參數(shù)進(jìn)行控制:-Xnoclassgc, 是否開啟類回收蔓挖。
總結(jié)
對象存活判定算法
- 引用計(jì)數(shù)法
- 原理:對象中設(shè)置一個引用計(jì)數(shù)器夕土,當(dāng)有地方引用它時,計(jì)數(shù)器+1,引用失效時怨绣,計(jì)數(shù)器-1角溃,當(dāng)計(jì)數(shù)器==0時,對象已死篮撑。
- 優(yōu)點(diǎn):實(shí)現(xiàn)簡單减细,判定效率高
- 缺點(diǎn):無法解決對象間直接相互循環(huán)引用問題。
- 可達(dá)性分析算法
- 原理:一系列的
GC Roots
對象作為起始點(diǎn)赢笨,向下搜索未蝌,走過的路徑被稱為引用鏈。當(dāng)一個對象到GC Roots沒有一個引用鏈相連茧妒,或者說GC Roots到對象不可達(dá)時萧吠,對象已死 - 優(yōu)點(diǎn):解決對象間直接相互循環(huán)引用的問題
- GC Roots對象:
- 虛擬機(jī)棧中引用的對象,即棧幀中本地變量表的引用對象
- 本地方法棧中JNI,即Native方法中對象
- 方法區(qū)中靜態(tài)變量引用的對象
- 方法區(qū)中常量引用的對象
對象兩次標(biāo)記判定算法
- 原理:對象的fianlize方法可以第一次標(biāo)記已死的對象重新存活桐筏。
- 步驟:
- 判斷對象是否覆蓋finalize方法或者判斷虛擬機(jī)是否已經(jīng)執(zhí)行了finalize方法
- 如果沒有覆蓋finalize方法或已經(jīng)執(zhí)行了finalize方法纸型,則對象必死
- 如果對象覆蓋了finalize方法且虛擬機(jī)沒有執(zhí)行finalize方法,將對象放到F-Queue隊(duì)列中
- 稍后虛擬機(jī)自動建立低優(yōu)先級的線程梅忌,執(zhí)行F-Queue隊(duì)列中對象的finalize方法狰腌,因虛擬機(jī)資源和效率,不一定會等待所有對象的finalize方法執(zhí)行完成牧氮。
- 稍候GC對F-Queue隊(duì)列中對象進(jìn)行第二次標(biāo)記琼腔,如果對象在finalize方法中與引用鏈重新建立鏈接,即第二次標(biāo)記存活的對象蹋笼,移出待回收集合展姐。如果第二次標(biāo)記還是死亡,則對象必死剖毯。
方法區(qū)回收判定算法
- 常量判定
- 常量內(nèi)容:字面量和符號引用
- 判定算法:
- 堆中沒有對象引用該字面量或符號引用
- 其他地方?jīng)]有引用該字面量或符號引用
- 類判定
- 虛擬機(jī)啟用類回收卸載
- 使用參數(shù):-Xnoclassgc圾笨,啟用類回收卸載
- 類信息同時滿足3個條件判定可卸載
- 堆中沒有該類的任何實(shí)例對象
- 該類的ClassLoader被卸載
- 該類對應(yīng)的java.lang.Class對象沒有在任何地方被引用,無法通過反射機(jī)制引用到該類
- 虛擬機(jī)啟用類回收卸載
垃圾回收算法
垃圾回收算法有哪幾種
- 標(biāo)記清除
- 復(fù)制算法
- 標(biāo)記整理
- 分代收集
什么是標(biāo)記清除算法
- 標(biāo)記:標(biāo)記出所有待回收的對象逊谋。
- 清除:標(biāo)記完成擂达,統(tǒng)一回收被標(biāo)記對象的內(nèi)存。
標(biāo)記清除算法有什么優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):實(shí)現(xiàn)簡單
- 缺點(diǎn):
- 標(biāo)記和清除的效率都不高胶滋。
- 一次GC后會產(chǎn)生大量的不連續(xù)的內(nèi)存碎片板鬓,當(dāng)分配大對象時,可能會無法分配內(nèi)存而重新發(fā)起一次GC
什么是復(fù)制算法
- 將堆內(nèi)存分為大小相等兩塊區(qū)域究恤,每次只用其中一塊
- 當(dāng)使用的那一塊內(nèi)存使用完畢俭令,觸發(fā)一次GC, 將存活的對象復(fù)制到另外一塊,已使用過的內(nèi)存空間一次清理部宿。
- 復(fù)制時只移動堆頂指針抄腔,按順序分配內(nèi)存瓢湃,不會有大量內(nèi)存碎片出現(xiàn)
復(fù)制算法有什么優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):解決了標(biāo)記清除算法的效率問題和內(nèi)存碎片問題
- 缺點(diǎn):一次只能用一半的內(nèi)存空間,空間消耗太大
復(fù)制算法如何優(yōu)化內(nèi)存空間消耗太大的問題
IBM的研究表明: 98%的對象朝生夕死赫蛇,熬過一次GC的對象極少绵患,所以并不需要按1:1等比例來劃分內(nèi)存空間。
內(nèi)存劃分:
- Eden空間:占內(nèi)存80%悟耘,數(shù)量1
- Survivor空間:占內(nèi)存10%落蝙,數(shù)量2
- From空間
- To空間
每次使用Eden空間和Survivor空間中一個,即使用90%的空間暂幼,剩余10%的空間筏勒。
畢竟98%只是一般理論數(shù)據(jù),10%的空間足夠存放理論上2%的存活對象粟誓。
發(fā)生GC時奏寨,將存活對象(理論上2%)復(fù)制到Survivor的另一塊空閑空間,Eden空間和已使用的一塊Survivor空間回收內(nèi)存鹰服。
98%畢竟是理論值病瞳,如果超過10%的對象熬過GC, 特別是大對象,那該怎么辦
98%的對象朝生夕死悲酷,根據(jù)對象生存周期的不同可以將java堆內(nèi)存分為兩種:新生代和老年代
套菜。
開始內(nèi)存都在新生代中分配,每熬過一次GC, 對象年齡+1设易,當(dāng)對象年齡到15歲時逗柴,移到老年代。
很顯然顿肺,新生代的對象適合用復(fù)制算法戏溺。但如果10%的空間不夠,會用老年代的空間進(jìn)行擔(dān)保屠尊,進(jìn)入老年代的空間旷祸。
能熬過15次GC的老年代中對象,存活率肯定比較高讼昆,用復(fù)制算法的10%空間根本不夠吧
是的托享,老年代中對象存活率很高,不適合使用復(fù)制算法浸赫,所以使用標(biāo)記整理算法闰围。
什么是標(biāo)記整理算法
- 標(biāo)記:對所有可回收的對象進(jìn)行標(biāo)記
- 整理:標(biāo)記完成后,所有存活的對象向一端移動既峡,然后直接清理存活對象邊界之外的內(nèi)存空間羡榴。
標(biāo)記整理算法有什么優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):解決了標(biāo)記整理的大量內(nèi)存碎片的問題,適合老年代运敢。老年代對象存活率高炕矮,沒有額外空間對它進(jìn)行分配擔(dān)保么夫。
- 缺點(diǎn):不適合新生代者冤。新生代對象存活率低肤视,沒有復(fù)制算法效率高。
那新生代就用復(fù)制算法涉枫,老年代就用標(biāo)記整理算法就好了
是的邢滑,這就是分代算法
- 新生代對象存活率低,使用復(fù)制算法愿汰,只需復(fù)制少量對象就完成收集困后。
- 老年代存活率高,且沒有額外空間擔(dān)保衬廷,必須用標(biāo)記整理算法回收空間摇予。
總結(jié)
堆內(nèi)存分代
- 原因:98%的對象朝生夕死
- 分代:
- 新生代
- 存放對象:對象起始都在新生代分配,每熬過一次GC,對象年齡+1吗跋,對象到15歲侧戴,移到老年代。
- 復(fù)制算法空間劃分:使用復(fù)制算法對新生代空間劃分
- Eden空間:占用新生代80%跌宛,1個酗宋,使用的空間。
- Survivor空間:占用新生代20%疆拘,2個蜕猫,使用其中之一。
- From空間哎迄,占用新生代10%
- To空間回右,占用新生代10%
- 老年代
垃圾收集算法
- 標(biāo)記清除
- 原理:標(biāo)記所有待回收的對象,標(biāo)記完成漱挚,清理所有待回收對象內(nèi)存空間翔烁。
- 優(yōu)點(diǎn):實(shí)現(xiàn)簡單
- 缺點(diǎn):標(biāo)記和清理效率不高,而且產(chǎn)生大量內(nèi)存碎片棱烂。
- 復(fù)制算法
- 原理:
- 將新生代分為兩部分空間租漂,一塊Eden空間(占用80%空間)和兩塊Survivor空間(分別占用10%空間),每次只使用Eden空間和一塊Survivor空間颊糜,即使用90%空間哩治。
- 發(fā)生GC時,將存活的對象復(fù)制到另一塊空閑的Survivor空間衬鱼,Eden空間和已使用的Survivor空間一次清理业筏。
- 優(yōu)點(diǎn):適合收集新生代朝生夕死的對象,只需復(fù)制少量的存活對象完成收集鸟赫,且不會產(chǎn)生大量內(nèi)存碎片蒜胖。
- 缺點(diǎn):存活對象超過10%時消别,需要擔(dān)保進(jìn)入老年代。
- 原理:
- 標(biāo)記整理
- 原理:標(biāo)記所有待回收對象台谢,標(biāo)記完成寻狂,將存活對象移到一端,端邊界之外的內(nèi)存空間一次清理朋沮。
- 優(yōu)點(diǎn):適合對象存活率高蛇券,且無法擔(dān)保的老年代。也不會產(chǎn)生大量內(nèi)存碎片樊拓。
- 缺點(diǎn):標(biāo)記和整理效率不高纠亚。
- 分代算法
- 原理:在新生代使用復(fù)制算法,只需復(fù)制少量對象即可完成收集筋夏。在老年代使用標(biāo)記整理算法蒂胞,老年代對象存活率高,且無法擔(dān)保条篷。
想共同學(xué)習(xí)jvm的可以加我微信:1832162841骗随,或者進(jìn)QQ群:982523529