垃圾收集器與內(nèi)存分配策略

垃圾收集器與內(nèi)存分配策略

@(Java虛擬機)[垃圾收集, GC]

[TOC]

對象已死嗎

程序計數(shù)器贸呢,虛擬機棧冯键,本地方法棧隨線程回收而回收窄坦,而Java堆和方法區(qū)不會回收拴驮,對象也是動態(tài)創(chuàng)建春瞬。這部分區(qū)域是垃圾回收的主要區(qū)域。

引用計數(shù)算法

給對象添加一個引用計數(shù)器套啤,每當(dāng)引用時宽气,計數(shù)器加1,引用失效計數(shù)器減1潜沦。為0的對象就是不在使用的萄涯。引用計數(shù)簡單有效率,但是主要無法解決對象之間的相互循環(huán)引用問題止潮。
eg:A.a=B.b; B.b=A.a

可達(dá)性分析算法

可達(dá)性分析以'GC Roots'對象作為起點窃判,向下搜索钞楼,走過路徑稱為引用鏈喇闸。一個對象到GC Roots沒有任何引用鏈即從GC Roots到對象不可達(dá),則該對象不可用询件。


可達(dá)性分析算法判定對象是否可回收

在Java語言中可以作為GC Roots的對象包括下面幾種:

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象燃乍。
  • 方法區(qū)中類靜態(tài)屬性引用的對象。
  • 方法區(qū)中常量引用的對象宛琅。
  • 本地方法棧中JNI(Native方法)引用的對象刻蟹。

再談引用

判斷對象是否存活與“引用”有關(guān)。在JDK1.2前嘿辟,對引用定義很簡單舆瘪,如果reference類型的數(shù)據(jù)存儲的數(shù)值代表另外一塊內(nèi)存的起始地址片效,這塊內(nèi)存代表著一個引用。在JDK1.2以后Java對引用概念進行擴充英古,將引用分為4種:(強度依次減弱)

  • 強引用(Strong Reference) eg:Object obj=new Object()淀衣;強引用還存在,就不回收被引用的對象召调。
  • 軟引用(Soft Reference)有用但非必須對象膨桥,系統(tǒng)在發(fā)生內(nèi)存溢出異常前,會將這些對象列進回收范圍中唠叛。
  • 弱引用(Weak Reference)非必須對象只嚣,下次垃圾收集回收。
  • 虛引用(Phantom Reference)對象是否有虛引用不會對其生存時間構(gòu)成影響艺沼,也無法通過虛引用取得對象實例册舞,設(shè)置虛引用的作用在于這個對象被垃圾收集器回收時收到一個系統(tǒng)通知。

生存還是死亡

在可達(dá)性分析算法中不可達(dá)的對象障般,也并非“非死不可”环础,對象暫時處于“緩刑階段”,真正死亡需要至少兩次的標(biāo)記過程剩拢。
對象不可達(dá)會被第一次標(biāo)記并且篩選线得,篩選條件是對象是否需要執(zhí)行finalize方法。(對象沒有覆蓋finalize方法徐伐,或者finalize已經(jīng)被虛擬機觸發(fā)過贯钩,虛擬機則認(rèn)為是沒有必要執(zhí)行)

如果需要執(zhí)行finalize方法的,對象會被放入F-Queue隊列中办素,并且會被虛擬機創(chuàng)建的Finalizer(低優(yōu)先級)線程去調(diào)用finalize方法角雷。稍后GC會對F-Queue中的對象進行第二次小規(guī)模標(biāo)記,如果在finalize過程中該對象又和可達(dá)對象建立了關(guān)聯(lián)性穿,則在第二次標(biāo)記時會被移除“即將回收”集合勺三。

注:
1.finalize方法只會被系統(tǒng)調(diào)用一次。如果對象被調(diào)用過此方法需曾,而且面臨下次回收吗坚,它的finalize不會被執(zhí)行。
2.finalize不確定性大呆万,實用性不大商源。回收工作不如try-finally等方式谋减。建議忘記此方法的存在牡彻。

回收方法區(qū)

方法區(qū)(或者HotSpot的永久代)垃圾回收主要是兩部分內(nèi)容:廢棄常量和無用的類。

廢棄常量判定:字符串“abc”在常量池中出爹,但系統(tǒng)沒有一個String對象指向它庄吼。常量池中其他類(接口)缎除,方法,字段的符號引 用與此類似总寻。

無用類判定:3個條件

  • 該類實例已全部回收伴找。Java堆中不存在該類實例了。
  • 加載該類的ClassLoader已經(jīng)被回收废菱。
  • 該類對應(yīng)的java.lang.Class對象沒有在任何地方被引用技矮。無法在任何地方通過反射訪問該類的方法

滿足上述條件后虛擬機可以對無用類回收了。但不一定必然會回收殊轴。是否對類回收HotSpot提供-Xnnclassgc參數(shù)控制衰倦。

垃圾收集算法

標(biāo)記-清除算法

先標(biāo)記,在清除旁理。不足之處:1.效率問題樊零,標(biāo)記和清除效率都不高。2.空間問題孽文,清除后產(chǎn)生不連續(xù)內(nèi)存碎片驻襟。


標(biāo)記-清除算法

復(fù)制算法

將內(nèi)存分為兩塊,每次使用一塊芋哭,一塊內(nèi)存用完時沉衣,就將還存活的對象復(fù)制到另外一塊上。前面內(nèi)存空間一次清理掉减牺。
不足:浪費內(nèi)存空間豌习。存活對象多時,復(fù)制效率不高拔疚。

優(yōu)化方案:
大部分對象死得快肥隆。劃分比例改一改:HotSpot--> 8(Eden)+1(Survivor)+1(Survivor)。浪費最后一份Survivor即可稚失。
缺點:極端情況栋艳,1份裝不下存活的對象,就需要其他內(nèi)存空間做擔(dān)保句各。


復(fù)制算法

標(biāo)記-整理算法

和標(biāo)記-清除算法類似吸占,但是后續(xù)操作不是清理而是讓存活對象移動到一端。清理端邊界以外的內(nèi)存


標(biāo)記-整理算法

分代收集算法

當(dāng)前商業(yè)虛擬機都采用此算法诫钓。根據(jù)對象的存活周期不同將內(nèi)存劃分為幾塊旬昭。將Java堆分為新生代和老年代篙螟,在新生代中使用復(fù)制算法菌湃,在老年代中使用標(biāo)記-清理或者標(biāo)記-整理算法。

HotSpot的算法實現(xiàn)

枚舉根節(jié)點

使用OopMap來記錄那些位置是引用遍略,不需要對整個全局性引用和棧幀的本地變量表遍歷惧所。節(jié)約時間骤坐。
GC時需要暫所有的Java執(zhí)行線程使分析可靠。

安全點

在OopMap的協(xié)助下下愈,HotSpot快速完成GC Roots枚舉纽绍。為了節(jié)約空間不是所有的指令會產(chǎn)生OopMap。安全點位置才會產(chǎn)生势似,也是在安全點下才能GC拌夏。
安全點選取條件:是否具有讓程序長時間執(zhí)行的特征。

長時間執(zhí)行的特征是指令序列復(fù)用履因,例如方法調(diào)用障簿,循環(huán)跳轉(zhuǎn),異常跳轉(zhuǎn)等栅迄。這些地方的指令才會產(chǎn)生SafePoint站故。

GC發(fā)生時讓所有線程都跑到安全點停下來:
搶先式中斷:GC發(fā)生所有線程中斷,如果線程中斷不在安全點上毅舆,則恢復(fù)線程跑到安全點上西篓。基本沒有虛擬機這樣做
主動式中斷:設(shè)置標(biāo)志憋活,GC發(fā)生時岂津,線程輪詢這個標(biāo)志,中斷標(biāo)志為真則自己中斷悦即,輪詢標(biāo)志的地方和安全點重合寸爆。

安全區(qū)域

線程不執(zhí)行時(處于Sleep和Blocked下),無法響應(yīng)JVM的中斷請求盐欺。這種情況需要安全區(qū)域配合赁豆。
安全區(qū)域:指這段代碼片段中不會引起引用關(guān)系的變化。在這區(qū)域中GC都是安全的冗美。
線程執(zhí)行到Safe Region中時魔种,首先標(biāo)記自己是進入Safe Region狀態(tài)。當(dāng)要離開Safe Region時需要檢查是否完成了GC粉洼,沒有完成就必須等待可以離開信號為止节预。

垃圾收集器

圖中展示7中收集器在不同分代中工作。連線代表搭配使用属韧。


HotSpot虛擬機的垃圾收集器

Serial收集器

Serial收集器不僅僅是使用一個CPU或者一條線程去完成垃圾收集安拟,更重要的是它收集垃圾時必須暫停其他工作線程。
Serial收集器在Client模式下是很好的選擇
新生代采用復(fù)制算法宵喂,暫停所有用戶線程糠赦,老年代采用標(biāo)記-整理算法,暫停所有用戶線程


Serial/Serial Old收集器運行示意圖

ParNew收集器

ParNew收集器是Serial的多線程版本。和Serial收集器沒有太多創(chuàng)新之處拙泽。但它是Server模式下的虛擬機的首選新生代收集器淌山。ParNew是除了serial外能和CMS(Concurrent Mark Sweep)搭配使用的收集器。ParNew在單CPU下效果不會有Serial的好顾瞻。


ParNew/Serial Old收集器運行示意圖

Parallel Scavenge收集器

Parallel Scavenge是新生代收集器泼疑,使用復(fù)制算法,并行的多線程收集器荷荤。
Parallel Scaveng收集器關(guān)注點和其他收集器不一樣退渗,CMS等收集器的關(guān)注點是盡可能縮短垃圾收集時用戶線程的停頓時間。而此收集器關(guān)注吞吐量蕴纳。
吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)氓辣。
Parallel Scavenge可以使用GC自適應(yīng)的調(diào)節(jié)策略來自動動態(tài)調(diào)整參數(shù)。

Serial Old收集器

Serial Old是Serial的老年代版本袱蚓,也是單線程收集器钞啸,使用標(biāo)記-整理算法。主要給Client模式的虛擬機使用喇潘。

Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本体斩,使用多線程和“標(biāo)記-整理”算法。

CMS收集器

CMS(Concurrent Mark Sweep)收集器以獲取最短回收停頓時間為目標(biāo)颖低。主要應(yīng)用于B/S系統(tǒng)上絮吵。CMS采用標(biāo)記-清除算法實現(xiàn)。分4個步驟:

  • 初始標(biāo)記
  • 并發(fā)標(biāo)記
  • 重新標(biāo)記
  • 并發(fā)清除

初始標(biāo)記和重新標(biāo)記需要Stop the world忱屑,初始標(biāo)記GC Roots能直接關(guān)聯(lián)的對象蹬敲,速度快。并發(fā)標(biāo)記進行GC Roots Tracing過程莺戒。重新標(biāo)記對在并發(fā)標(biāo)記過程中有部分對象標(biāo)記產(chǎn)生變動的一部分重新標(biāo)記伴嗡。時間比初始標(biāo)記長,但比并發(fā)標(biāo)記短得多从铲。
CMS過程中耗時較長的部分是并發(fā)的瘪校,所以整體上能和用戶線程一起并發(fā)執(zhí)行。

CMS收集器運行示意圖

CMS缺點:

  1. 對CPU資源非常敏感(并發(fā)導(dǎo)致)名段,CMS默認(rèn)啟動回收線程數(shù)(CPU數(shù)量+3)/4阱扬,CPU數(shù)量少于4個時,影響很大伸辟。
  2. 無法處理浮動垃圾麻惶,可能導(dǎo)致Concurrent Mode Failure 失敗導(dǎo)致另一次Full GC。
  3. 標(biāo)記-清除算法產(chǎn)生大量碎片信夫,內(nèi)存空間連續(xù)的無法分配大對象時需要Full GC窃蹋。

G1收集器

G1是面向服務(wù)端應(yīng)用的收集器卡啰,G1具備的特點:

  • 并行與并發(fā):充分利用多CPU,多核硬件優(yōu)勢脐彩。
  • 分代收集:可以獨立管理整個GC堆碎乃,不需其他收集器配合姊扔。
  • 空間整合:G1整體看來是基于標(biāo)記——整理算法實現(xiàn)惠奸。從局部看是基于復(fù)制算法
  • 可預(yù)測停頓:可指定在M毫秒中,垃圾收集不超過N毫秒

G1將內(nèi)存分為多個大小相等的獨立區(qū)域Region恰梢,還保留新生代和老年代的概念佛南,但不再是物理隔離的,都是是一部分Region(不需連續(xù))的集合嵌言。G1避免在全區(qū)域垃圾回收嗅回,可以對單個Region回收,后臺維護一個優(yōu)先列表摧茴,來決定回收那些Region绵载。

Remembered Set步驟:保證不對全堆掃描也不會遺漏。記錄在不同Region中的對象引用苛白。
G1收集器步驟:

  • 初始標(biāo)記
  • 并發(fā)標(biāo)記
  • 最終標(biāo)記
  • 篩選回收
G1運行示意圖

理解GC日志

GC日志

33.3125和100.667代表GC發(fā)生時間娃豹,Java虛擬機啟動以來的秒數(shù)。
Full GC代表stop the world
DefNew购裙,Tenured懂版,Perm代表GC發(fā)生區(qū)域。顯示的區(qū)域名和GC收集器相關(guān)躏率,DefNew代表Serial新生代躯畴,ParNew代表Parallel新生代,PSYoungGen代表Parallel Scavenge新生代薇芝。
3324K-》152K(3721K):GC前該區(qū)域內(nèi)存區(qū)域已使用容量-》GC后改內(nèi)存區(qū)域已使用容量(該內(nèi)存區(qū)域總?cè)萘浚?br> 3324K-》152K(11904K):GC前Java堆已使用容量-》GC后Java堆已使用容量(Java堆總?cè)萘浚?br> 0.0025925secs表示該內(nèi)存區(qū)域GC所占用的時間蓬抄。單位秒

垃圾收集相關(guān)常用參數(shù)

1.png

2.png

內(nèi)存分配與回收策略

對象主要分配在新生代Eden區(qū)上,少數(shù)情況會分配在老年代中夯到。規(guī)則不固定倡鲸,細(xì)節(jié)由垃圾收集器和參數(shù)決定。
下面使用Serial/Serial Old收集器的內(nèi)存分配和回收策略黄娘。

對象優(yōu)先在Eden分配

   private static final int _1MB=1024*1024;
    /**
     * VM參數(shù):-verbose:gc -Xms20M -Xmx20M -Xmn10M 限制Java堆大小為20M峭状,不可擴展,10M新生代逼争,10M老年代
     * -XX:+PrintGCDetails
     *  -XX:SurvivorRatio=8決定新生代中Eden區(qū)與Survivor區(qū)=8:1
     */
    public static void testAllocation(){
        byte[] allocation1,allocation2,allocation3,allocation4;
        allocation1=new byte[2*_1MB];
        allocation2=new byte[2*_1MB];
        allocation3=new byte[2*_1MB];
        allocation4=new byte[4*_1MB]; //出現(xiàn)一次Minor GC
    }

eden space 8192K优床,from space 1024K,to space 1024K誓焦,新生代9216K胆敞。當(dāng)分配allocation4時着帽,內(nèi)存不足,發(fā)生Minor GC移层,1,2,3被轉(zhuǎn)到老年區(qū)仍翰,然后4被分配在Eden中。Survivor空閑观话。
新生代GC(Minor GC):發(fā)生頻繁予借,速度快
老年代GC(Major/Full GC):速度慢,一般伴隨Minor GC

大對象直接進入老年代

虛擬機提供-XX:pretenureSizeThreshold參數(shù)频蛔,大于這個值的對象直接分配到老年代灵迫,避免在Eden和兩個Survivor發(fā)生復(fù)制。
注:pretenureSizeThreshold只對Serial和ParNew有效晦溪。

長期存活對象將進入老年代

給對象定義了對象年齡計數(shù)器瀑粥,對象在Eden出生并在minor GC后任存活,并被Survivor容納三圆,將移動到Survivor中狞换,對象年齡設(shè)為1,每過一次Minor GC舟肉,年齡加1修噪。默認(rèn)到15,就會轉(zhuǎn)入老年代。參數(shù)值可用-XX:MaxTenuringThreshold設(shè)置度气。

動態(tài)對象年齡判定

如果Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半割按,年齡大于該年齡的對象進入老年代。就不需MaxTenuringThreshold中要求的年齡磷籍。

空間分配擔(dān)保

在Minor GC前适荣,虛擬機先檢查老年代連續(xù)可用空間是否大于新生代對象總空間,如果成立院领,則Minor GC安全弛矛。不成立
虛擬機先查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗。允許則檢查老年代連續(xù)空間是否大于歷次轉(zhuǎn)入老年代對象的平均值比然。大于則Minor GC(有風(fēng)險)丈氓。小于或者HandlePromotionFailure不允許冒險,則Full GC强法。JDK6 Update14后HandlePromotionFailure參數(shù)不在使用万俗。
風(fēng)險是因為老年代需要做擔(dān)保。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饮怯,一起剝皮案震驚了整個濱河市闰歪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蓖墅,老刑警劉巖库倘,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件临扮,死亡現(xiàn)場離奇詭異,居然都是意外死亡教翩,警方通過查閱死者的電腦和手機杆勇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饱亿,“玉大人蚜退,你說我怎么就攤上這事÷放酰” “怎么了关霸?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵传黄,是天一觀的道長杰扫。 經(jīng)常有香客問我,道長膘掰,這世上最難降的妖魔是什么章姓? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮识埋,結(jié)果婚禮上凡伊,老公的妹妹穿的比我還像新娘。我一直安慰自己窒舟,他們只是感情好系忙,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著惠豺,像睡著了一般银还。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上洁墙,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天蛹疯,我揣著相機與錄音,去河邊找鬼热监。 笑死捺弦,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的孝扛。 我是一名探鬼主播列吼,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼苦始!你這毒婦竟也來了寞钥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤盈简,失蹤者是張志新(化名)和其女友劉穎凑耻,沒想到半個月后太示,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡香浩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年类缤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邻吭。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡餐弱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出囱晴,到底是詐尸還是另有隱情膏蚓,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布畸写,位于F島的核電站驮瞧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏枯芬。R本人自食惡果不足惜论笔,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望千所。 院中可真熱鬧狂魔,春花似錦、人聲如沸淫痰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽待错。三九已至籽孙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間朗鸠,已是汗流浹背蚯撩。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留烛占,地道東北人胎挎。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像忆家,于是被迫代替她去往敵國和親犹菇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容