jvm--Garbage Collection
垃圾回收(GC)一直是java語言的重中之重。
1 對(duì)象狀態(tài)鑒別
1.1 標(biāo)記對(duì)象是否可回收一般有兩種算法:
- 引用計(jì)數(shù)算法:給每個(gè)對(duì)象添加一個(gè)引用計(jì)數(shù)器,當(dāng)引用一次時(shí)+1,當(dāng)引用時(shí)效時(shí)-1,當(dāng)計(jì)數(shù)器為0時(shí)即可回收。該算法最大的缺點(diǎn)是當(dāng)多個(gè)對(duì)象相互循環(huán)引用時(shí)將用不釋放。
- 可達(dá)性分析算法:將“GC Roots”作為起點(diǎn)向下搜索,搜索所走過的路徑稱為引用鏈,當(dāng)對(duì)象與GC roots沒有任何連接時(shí)即可釋放。GC Roots包含如下四種對(duì)象:虛擬機(jī)棧(戰(zhàn)陣中的本地變量表)中引用的對(duì)象、方法區(qū)中類靜態(tài)屬性引用的對(duì)象、方法區(qū)中常量引用的對(duì)象、本地方法棧中JNI(Native方法)引用的對(duì)象。
1.2 引用
在JDK1.2以前,java中應(yīng)用的定義很狹義:如果reference類型的數(shù)據(jù)中存儲(chǔ)的數(shù)值代表的是另外一塊內(nèi)存的起始地址,就稱這塊內(nèi)存代表著一個(gè)引用;在JDK1.2之后,java對(duì)引用的概念進(jìn)行了擴(kuò)充分為如下四類:
- 強(qiáng)引用:代碼中普遍存在的,如“Object obj = new Object()”這類,只要強(qiáng)引用存在,垃圾回收器永遠(yuǎn)不會(huì)回收被引用的對(duì)象。
- 軟引用:用來描述一些還有用但并非必須的對(duì)象,在系統(tǒng)發(fā)生內(nèi)存溢出之前,將這些對(duì)象列入可回收隊(duì)列進(jìn)行第二次回收。
- 弱引用:用來描述非必需的對(duì)象,強(qiáng)度比軟引用稍弱,無論當(dāng)前內(nèi)存是否充足,弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾回收之前。
- 虛引用:最弱的一種引用關(guān)系,完全不會(huì)對(duì)關(guān)聯(lián)對(duì)象的生存時(shí)間產(chǎn)生影響,也無法通過虛引用獲取一個(gè)對(duì)象的實(shí)例,完全是為了在這個(gè)對(duì)象被回收時(shí)收到一個(gè)系統(tǒng)通知。
1.3 兩次確認(rèn)(不推薦使用finalize()方法)
在可達(dá)性分析中不可達(dá)的對(duì)象也不是一定被回收,一個(gè)對(duì)象被宣判死刑,至少要經(jīng)歷兩次標(biāo)記過程:如果對(duì)象在進(jìn)行可達(dá)性分析時(shí)發(fā)現(xiàn)沒有與GC Roots相連,它將會(huì)被將第一次標(biāo)記并且進(jìn)行一次篩選,篩選的條件是此對(duì)象是否有必要執(zhí)行finalize()方法。當(dāng)對(duì)象沒有覆蓋finalize()方法或者finalize()方法已經(jīng)被虛擬機(jī)調(diào)用過,將視為“沒必要執(zhí)行”,立即回收。
當(dāng)這個(gè)對(duì)象被判定有必要執(zhí)行finalize()方法時(shí),這個(gè)對(duì)象將會(huì)放置在F-Queue隊(duì)列中,并在稍后由一個(gè)由虛擬機(jī)自動(dòng)建立的、低優(yōu)先級(jí)的Finalizer線程觸發(fā)對(duì)象的finalize()方法,但不承諾保證方法運(yùn)行結(jié)束。finalize()方法是對(duì)象逃脫死亡的最后一次機(jī)會(huì),稍后GC將對(duì)F-Queuezhongde對(duì)象進(jìn)行第二次小規(guī)模標(biāo)記,只要重新與GC Roots引用鏈上的任何一個(gè)對(duì)象建立關(guān)聯(lián)即可完成救贖,否則將被回收。
值得注意的是,任何一個(gè)對(duì)象的finalize()方法只會(huì)被系統(tǒng)自動(dòng)調(diào)用一次,此外finalize()方法并不推薦使用,而是應(yīng)該更多的使用try-finally等其他方式。
1.4 方法區(qū)回收
大多數(shù)人可能認(rèn)為方法區(qū)(HotSpot虛擬機(jī)中的永久代)是沒有垃圾收集的,其不然。永久代的垃圾回收只要收集“廢棄常量”和“無用的類”兩部分。
- 廢棄常量:與回收堆中的對(duì)象相似,以常量池中字面量的回收為例,如果一個(gè)字符串“abc”已經(jīng)進(jìn)入了常量池中,但當(dāng)前沒有任何一個(gè)String對(duì)象引用了“abc”常量,如果發(fā)生內(nèi)存回收,而且必要時(shí)將會(huì)將“abc”這個(gè)常量清除出常量池。常量池中的其他類、接口、方法的符號(hào)引用也與此類似。 無用的類:無用的類判斷條件相對(duì)苛刻很多,只有同時(shí)滿足如下三個(gè)條件才可算為無用的類。
- 該類的所有實(shí)例都已經(jīng)被回收,即java堆中不存在該類的任何實(shí)例
- 加載該類的classLoader已經(jīng)被回收
- 該類對(duì)應(yīng)的java.lang.Class對(duì)象沒有任何地方被應(yīng)用,無法在任何地方通過反射方位該類的方法
2 垃圾收集算法
- 標(biāo)記-清除算法? ?首先標(biāo)記需要回收的對(duì)象,然后在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象。具有兩個(gè)缺點(diǎn),其一,效率問題,標(biāo)記和清楚兩個(gè)階段效率都不高;其二,空間問題,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的碎片。(白色區(qū)域?yàn)槲词褂?#xff0c;綠色區(qū)域?yàn)榇婊顚?duì)象,紅色區(qū)域?yàn)榭苫厥?#xff09;
?
- 復(fù)制算法? 為了解決效率問題進(jìn)化而來。將可用內(nèi)存劃分為大小相等的兩塊,每次使用其中的一塊,當(dāng)這一塊內(nèi)存用完就將還存活的對(duì)象復(fù)制到另一塊內(nèi)存上,然后將已使用的內(nèi)存一次性清空,這樣不必考慮內(nèi)存碎片問題。現(xiàn)在的商業(yè)虛擬機(jī)都采用這種收集方法回收新生代內(nèi)存,俱IBM公司調(diào)研新生代中的對(duì)象98%是“朝生夕死”的,所以并不需要1:1劃分,而是將內(nèi)存劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和一塊Survivor。當(dāng)回收時(shí),Eden和Survivor中還存活的對(duì)象一次性復(fù)制到另一塊Survivor中,并其清理掉Eden和剛才使用的Survivor。在HotSpot中,Eden和Survivor的比例大小是8:1。
- 標(biāo)記-整理算法? 同樣首先標(biāo)記需要回收的對(duì)象,但后續(xù)步驟是讓存活的對(duì)象都向一端移動(dòng),然后直接清理掉邊界外的內(nèi)存。(白色區(qū)域?yàn)槲词褂?#xff0c;綠色區(qū)域?yàn)榇婊顚?duì)象,紅色區(qū)域?yàn)榭苫厥?#xff09;
- 分代收集算法? 根據(jù)對(duì)象存活周期的不同將內(nèi)存劃分為幾塊,一般分為新生代和老年代,根據(jù)各個(gè)年代的特點(diǎn)采用適當(dāng)?shù)氖謾C(jī)算法。新生代采用復(fù)制算法,老年代采用“標(biāo)記-清理”或者“標(biāo)記-整理”算法。
3 內(nèi)存分配與回收策略
java技術(shù)體系中所提倡的自動(dòng)內(nèi)存管理最終可歸結(jié)為自動(dòng)化地解決了兩個(gè)問題:給對(duì)象分配內(nèi)存以及回收分配給對(duì)象的內(nèi)存。對(duì)象的內(nèi)存分配往大方向講就是在堆上分配,主要是在新生代的Eden區(qū),如果啟動(dòng)了本地線程分配緩沖,將按線程優(yōu)先在TLBA上分配。內(nèi)存分配具有幾條普遍的分配規(guī)則:
- 對(duì)象優(yōu)先在Eden分配,當(dāng)Eden區(qū)沒有足夠空間進(jìn)行分配時(shí),虛擬機(jī)將發(fā)起一次Minor GC
- 大對(duì)象直接進(jìn)入老年代,大對(duì)象就是需要大量連續(xù)空間的java對(duì)象,最典型的是長字符串或數(shù)組
- 長存活期的對(duì)象將進(jìn)入老年代,對(duì)象經(jīng)過一次Minor GC存活并被Survivor容納,該對(duì)象年齡置為1,以后沒熬過一次Minor GC,年齡就加1,當(dāng)?shù)揭欢ǔ潭染蜁?huì)晉升到老年代
- 動(dòng)態(tài)對(duì)象年齡判定,虛擬機(jī)并不是永遠(yuǎn)要求只有到年齡才能晉升老年代,如果相同年齡所有對(duì)象的大小總和大于Survivor空間一半,則其他大于該年齡的對(duì)象可以直接進(jìn)入老年代
- 空間分配擔(dān)保,在發(fā)生Minor GC之前,虛擬機(jī)會(huì)先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)總空間,如果條件成立,Minor GC可以確保安全,否則就可能需要一次Full GC
Minor GC 和 Full GC 區(qū)別
- 新生代GC(Minor GC):發(fā)生在新生代的垃圾收集動(dòng)作,相對(duì)較頻繁,回收速度也較快
- 老年代GC(Major GC / Full GC):發(fā)生在老年代的GC,出現(xiàn)了Major GC,經(jīng)常會(huì)伴有至少一次Minor GC,Major GC速度一般比Minor GC慢10倍以上
4 HotSpot的GC算法實(shí)現(xiàn)
- 枚舉根節(jié)點(diǎn)? 可達(dá)性分析對(duì)于執(zhí)行時(shí)間的敏感體現(xiàn)在GC停頓上,因?yàn)檫@項(xiàng)分析工作必須在一個(gè)能確保一致性的快照中進(jìn)行(這里的一致性是指整個(gè)分析過程中整個(gè)執(zhí)行系統(tǒng)看起來就像被凍結(jié)在某個(gè)時(shí)間點(diǎn)上,不可以出現(xiàn)分析過程中對(duì)象引用關(guān)系還在不斷變化的情況,以保證分析結(jié)果準(zhǔn)確性),這點(diǎn)也是GC進(jìn)行時(shí)必須停頓所有java執(zhí)行線程(stop the world)的一個(gè)重要原因。在Hotspot虛擬機(jī)中,為了提升可達(dá)性分析效率,當(dāng)執(zhí)行系統(tǒng)停頓下來后,通過一組稱為OopMap的數(shù)據(jù)結(jié)構(gòu)來直接得知那些地方存放著對(duì)象引用,以便快速統(tǒng)計(jì)對(duì)象狀態(tài)。
- 安全點(diǎn)? 在OopMap的幫助下,HotSpot可以準(zhǔn)確快速的完成GC Roots枚舉。HotSpot沒有為每條機(jī)器指令都生成OopMap,而是在某些特定的位置記錄這些信息,即為安全點(diǎn);也只有到達(dá)安全點(diǎn)才能停頓。如何在發(fā)生GC時(shí)所有線程都跑到最近的安全點(diǎn)停頓下來,有兩種方案可選:其一,搶斷式中斷(基本被拋棄),首先把所有線程中斷,如果發(fā)現(xiàn)有線程中斷的地方不在安全點(diǎn),就讓線程恢復(fù)并跑到安全點(diǎn);主動(dòng)式中斷,當(dāng)GC需要中斷時(shí),不直接對(duì)線程操作,而是設(shè)置與安全點(diǎn)位置重合的一個(gè)標(biāo)識(shí),每個(gè)線程執(zhí)行時(shí)主動(dòng)去輪訓(xùn)這個(gè)標(biāo)識(shí),當(dāng)發(fā)現(xiàn)標(biāo)識(shí)為true時(shí)將自己中斷掛起。
- 安全區(qū)域? 安全點(diǎn)可以保證程序執(zhí)行時(shí)在不長的時(shí)間內(nèi)就會(huì)進(jìn)入安全點(diǎn),但是對(duì)于處于sleep或blocked等狀態(tài)的沒有分配CPU時(shí)間的線程無法進(jìn)入安全點(diǎn),這時(shí)候需要安全區(qū)域來解決。安全區(qū)域指一段代碼片段中,引用關(guān)系不會(huì)發(fā)生變化,這個(gè)地方區(qū)域中的任意地方開始GC都是安全的。當(dāng)線程執(zhí)行到安全區(qū)域的代碼中時(shí),將標(biāo)識(shí)自己進(jìn)入安全區(qū)域,這樣JVM發(fā)起GC時(shí)就就不用管處在安全區(qū)域的進(jìn)程了。
5 收集器簡介
?
轉(zhuǎn)載于:https://www.cnblogs.com/climber1990/p/7689621.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的jvm--Garbage Collection的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring MVC+Mybatis 多
- 下一篇: Notes of fwt