想要提高程序員自身的內(nèi)功心法無非就是: 數(shù)據(jù)結(jié)構(gòu)跟算法 + 操作系統(tǒng) + 網(wǎng)絡(luò) 鲫趁,而所有的Java代碼都是在JVM上運(yùn)行的,了解了JVM好處就是:
寫出更好更健壯的代碼牲阁。
提高Java的性能赚爵,排除問題。
面試必問 疙描,要對知識有一定的深度 炉峰。
1、簡述JVM 內(nèi)存模型
從宏觀上來說JVM 內(nèi)存區(qū)域 分為三部分 線程共享區(qū)域 疼阔、 線程私有區(qū)域 瓜客、 直接內(nèi)存區(qū)域 。
1.1竿开、線程共享區(qū)域
1.1.1、堆區(qū)
堆區(qū)Heap是JVM中最大的一塊內(nèi)存區(qū)域玻熙,基本上所有的對象實(shí)例都是在堆上分配空間否彩。堆區(qū)細(xì)分為 年輕代 和 老年代 ,其中年輕代又分為Eden嗦随、S0列荔、S1 三個(gè)部分敬尺,他們默認(rèn)的比例是 8:1:1的大小。
1.1.1元空間
方法區(qū):
在 《Java虛擬機(jī)規(guī)范》中只是規(guī)定了有 方法區(qū) 這么個(gè) 概念 跟它的 作用 贴浙。 HotSpot 在JDK8之前 搞了個(gè) 永久代 把這個(gè)概念實(shí)現(xiàn)了砂吞。用來主要存儲類信息、常量池崎溃、靜態(tài)變量蜻直、JIT編譯后的代碼等數(shù)據(jù)。
PermGen(永久代)中類的元數(shù)據(jù)信息在每次 FullGC 的時(shí)候可能會(huì)被收集袁串,但成績很難令人滿意概而。而且為PermGen分配多大的空間因?yàn)榇鎯ι鲜龆喾N數(shù)據(jù)很難確定大小。因此官方在JDK8提出移除永久代囱修。
官方解釋移除永久代:
This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation.
即:移除永久代是為融合HotSpot JVM與 JRockit VM而做出的努力赎瑰,因?yàn)镴Rockit沒有永久代,不需要配置永久代破镰。
元空間:
在Java中用 永久代 來存儲類信息餐曼,常量,靜態(tài)變量等數(shù)據(jù)不是好辦法鲜漩,因?yàn)檫@樣很容易造成內(nèi)存溢出源譬。同時(shí)對永久代的性能調(diào)優(yōu)也很困難,因此在JDK8中 把 永久代 去除了宇整,引入了元空間 metaspace 瓶佳,原先的class、field等變量放入到metaspace鳞青。
總結(jié):
元空間的本質(zhì)和永久代類似霸饲,都是對JVM規(guī)范中方法區(qū)的實(shí)現(xiàn) 。不過元空間與永久代之間最大的區(qū)別在于: 元空間并不在虛擬機(jī)中臂拓,而是使用本地內(nèi)存 厚脉。因此,默認(rèn)情況下胶惰,元空間的大小僅受本地內(nèi)存限制傻工,但可以通過參數(shù)來指定元空間的大小。
1.2孵滞、直接內(nèi)存區(qū)域
直接內(nèi)存:
一般使用 Native 函數(shù)操作C++代碼來實(shí)現(xiàn)直接分配堆外內(nèi)存中捆,不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域坊饶。這塊內(nèi)存不受Java堆空間大小的限制泄伪,但是受本機(jī)總內(nèi)存大小限制所以也會(huì)出現(xiàn)OOM異常。分配空間后 避免了在Java堆區(qū)跟Native堆中來回復(fù)制數(shù)據(jù) 匿级,可以有效提高讀寫效率蟋滴, 但它的創(chuàng)建染厅、銷毀卻比普通Buffer慢 。
PS: 如果使用了 NIO 津函,本地內(nèi)存區(qū)域會(huì)被頻繁地使用肖粮,此時(shí) jvm內(nèi)存 ≈ 方法區(qū) + 堆 + 棧+ 直接內(nèi)存
1.3、線程私有區(qū)域
程序計(jì)數(shù)器尔苦、虛擬機(jī)棧涩馆、本地方法棧跟線程的聲明周期是一樣的。
1.3.1蕉堰、程序計(jì)數(shù)器
課堂上比如你正在看小說《誅仙》凌净,看到1412章節(jié)時(shí),老師喊你回答問題屋讶,這個(gè)時(shí)候你肯定要先應(yīng)付老師的問題冰寻,回答完畢后繼續(xù)接著看,這個(gè)時(shí)候你可以用書簽也可以憑借記憶記住自己在看的位置皿渗,通過這樣實(shí)現(xiàn)繼續(xù)閱讀斩芭。
落實(shí)到代碼運(yùn)行時(shí)候同樣道理, 程序計(jì)數(shù)器 用于記錄當(dāng)前線程下虛擬機(jī)正在執(zhí)行的字節(jié)碼的指令地址乐疆。它具有如下特性:
線程私有
多線程情況下划乖,在同一時(shí)刻所以為了讓線程切換后依然能恢復(fù)到原位,每條線程都需要有各自獨(dú)立的程序計(jì)數(shù)器挤土。
沒有規(guī)定OutOfMemoryError
程序計(jì)數(shù)器存儲的是字節(jié)碼文件的行號琴庵,而這個(gè)范圍是可知曉的,在一開始分配內(nèi)存時(shí)就可以分配一個(gè)絕對不會(huì)溢出的內(nèi)存仰美。
執(zhí)行Native方法時(shí)值為空
Native方法大多是通過C實(shí)現(xiàn)并未編譯成需要執(zhí)行的字節(jié)碼指令迷殿,也就不需要去存儲字節(jié)碼文件的行號了。
1.3.2咖杂、虛擬機(jī)棧
方法的出入棧:調(diào)用的方法會(huì)被打包成 棧楨 庆寺,一個(gè)棧楨至少需要包含一個(gè)局部變量表、操作數(shù)棧诉字、楨數(shù)據(jù)區(qū)懦尝、動(dòng)態(tài)鏈接。
動(dòng)態(tài)鏈接:
當(dāng)棧幀內(nèi)部包含一個(gè)指向運(yùn)行時(shí)常量池引用前提下壤圃,類加載時(shí)候會(huì)進(jìn)行符號引用到直接引用的解析跟鏈接替換陵霉。
局部變量表:
局部變量表是棧幀重要組中部分之一。他主要保存函數(shù)的參數(shù)以及局部的變量信息伍绳。局部變量表中的變量作用域是當(dāng)前調(diào)用的函數(shù)撩匕。函數(shù)調(diào)用結(jié)束后,隨著函數(shù)棧幀的銷毀墨叛。局部變量表也會(huì)隨之銷毀止毕,釋放空間。
操作數(shù)棧:
保存著Java虛擬機(jī)執(zhí)行過程中數(shù)據(jù)
方法返回地址:
方法被調(diào)用的位置漠趁,當(dāng)方法退出時(shí)候?qū)嶋H上等同于當(dāng)前棧幀出棧扁凛。
比如執(zhí)行簡單加減法:
執(zhí)行 javap -c *.class :
1.3.3、本地方法棧
跟虛擬機(jī)棧類似闯传,只是為使用到的Native方法服務(wù)而已谨朝。
2、判斷對象是否存活
JVM空間不夠就需要 Garbage Collection 了甥绿,一般共享區(qū)的都要被回收比如堆區(qū)以及方法區(qū)字币。在進(jìn)行內(nèi)存回收之前要做的事情就是 判斷那些對象是死的,哪些是活的 共缕。常用方法有兩種 引用計(jì)數(shù)法 跟 可達(dá)性分析 洗出。
2.1、引用計(jì)數(shù)法
思路是給 Java 對象添加一個(gè)引用計(jì)數(shù)器图谷,每當(dāng)有一個(gè)地方引用它時(shí)翩活,計(jì)數(shù)器 +1;引用失效則 -1便贵,當(dāng)計(jì)數(shù)器不為 0 時(shí)菠镇,判斷該對象存活;否則判斷為死亡(計(jì)數(shù)器 = 0)承璃。
優(yōu)點(diǎn):
實(shí)現(xiàn)簡單利耍,判斷高效。
缺點(diǎn):
無法解決 對象間 相互循環(huán)引用 的問題
step1: GcObject實(shí)例1的引用計(jì)數(shù)+1盔粹,實(shí)例1引用數(shù) = 1
step2: GcObject實(shí)例2的引用計(jì)數(shù)+1隘梨,實(shí)例2引用數(shù) = 1
step3: GcObject實(shí)例2的引用計(jì)數(shù)+1,實(shí)例2引用數(shù) = 2
step4: GcObject實(shí)例1的引用計(jì)數(shù)+1玻佩,實(shí)例1引用數(shù) = 2
step5: GcObject實(shí)例1的引用計(jì)數(shù)-1出嘹,結(jié)果為 1
step6: GcObject實(shí)例2的引用計(jì)數(shù)-1,結(jié)果為 1
如上分析發(fā)現(xiàn)實(shí)例1跟實(shí)例2的引用數(shù)都不為0而又相互引用咬崔,這兩個(gè)實(shí)例所占有的內(nèi)存則無法釋放税稼。
2.2、可達(dá)性分析
很多主流商用語言(如Java垮斯、C#)都采用 引用鏈法 判斷對象是否存活郎仆,大致的思路就是將一系列的 GC Roots 對象作為起點(diǎn),從這些起點(diǎn)開始向下搜索兜蠕。在Java語言中扰肌,可作為 GC Roots的對象包含以下幾種:
第一種是 虛擬機(jī)棧中的引用的對象 ,在程序中正常創(chuàng)建一個(gè)對象熊杨,對象會(huì)在堆上開辟一塊空間曙旭,同時(shí)會(huì)將這塊空間的地址作為引用保存到虛擬機(jī)棧中盗舰,如果對象生命周期結(jié)束了,那么引用就會(huì)從虛擬機(jī)棧中出棧桂躏,因此如果在虛擬機(jī)棧中有引用钻趋,就說明這個(gè)對象還是有用的,這種情況是最常見的剂习。
第二種是我們 在類中定義了全局的靜態(tài)的對象 蛮位,也就是使用了 static 關(guān)鍵字,由于虛擬機(jī)棧是線程私有的鳞绕,所以這種對象的引用會(huì)保存在共有的方法區(qū)中失仁,顯然將方法區(qū)中的靜態(tài)引用作為GC Roots是必須的。
第三種便是 常量引用 们何,就是使用了 static final 關(guān)鍵字萄焦,由于這種引用初始化之后不會(huì)修改,所以方法區(qū)常量池里的引用的對象也應(yīng)該作為GC Roots垂蜗。
第四種是在使用 JNI 技術(shù)時(shí)楷扬,有時(shí)候單純的Java代碼并不能滿足我們的需求,我們可能需要在Java中調(diào)用C或C++的代碼贴见,因此會(huì)使用 Native方法 烘苹,JVM內(nèi)存中專門有一塊本地方法棧,用來保存這些對象的引用片部,所以本地方法棧中引用的對象也會(huì)被作為GC Roots镣衡。
GC Root步驟主要包含如下三步:
2.1.1 可達(dá)性分析
當(dāng)一個(gè)對象到 GC Roots 沒有任何引用鏈相連時(shí) ,則判斷該對象不可達(dá)档悠。
注意: 可達(dá)性分析僅僅只是判斷對象是否可達(dá)廊鸥,但還不足以判斷對象是否存活 / 死亡。
2.1.2 第一次標(biāo)記 & 篩選
篩選的條件對象 如果沒有重寫finalize或者調(diào)用過finalize 則將該對象加入到F-Queue中
2.1.3 第二次標(biāo)記 & 篩選
當(dāng)對象經(jīng)過了第一次的標(biāo)記 & 篩選辖所,會(huì)被進(jìn)行第二次標(biāo)記 & 準(zhǔn)備被進(jìn)行篩選惰说。 經(jīng)過F-Queue篩選后如果對象還沒有跟GC Root建立引用關(guān)系則被回收 ,屬于給個(gè)二次機(jī)會(huì)缘回。
2.3吆视、四大引用類型
2.3.1 強(qiáng)引用
強(qiáng)引用(StrongReference)是使用最普遍的引用。垃圾回收器絕對不會(huì)回收它酥宴,內(nèi)存不足時(shí)寧愿拋出OOM導(dǎo)致程序異常啦吧,平常的new 對象就是。
2.3.2 軟引用
垃圾回收器在內(nèi)存充足時(shí)不會(huì)回收軟引用(SoftReference)對象拙寡,不足時(shí)會(huì)回收它授滓,特別適合用于創(chuàng)建緩存。
2.3.3 弱引用
弱引用(WeakReference)是在掃描到該對象時(shí)無論內(nèi)存是否充足都會(huì)回收該對象。 ThreadLocal 的Key就是弱引用般堆。
2.3.4 虛引用
如果一個(gè)對象只具有虛引用(PhantomReference)那么跟沒有任何引用一樣在孝,任何適合都可以被回收。主要用跟蹤對象跟垃圾回收器回收的活動(dòng)淮摔。
3浑玛、垃圾回收算法
為了揮手回收垃圾操作系統(tǒng)一般會(huì)使用 標(biāo)記清除 、 復(fù)制算法 噩咪、 標(biāo)記整理 三種算法,這三種各有優(yōu)劣极阅,簡單介紹下:
3.1胃碾、標(biāo)記清除
原理:
算法分為 標(biāo)記 和 清除 兩個(gè)階段:首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象筋搏。
缺點(diǎn):
標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片仆百,導(dǎo)致觸發(fā)GC。
3.2奔脐、標(biāo)記復(fù)制
原理:
將可用內(nèi)存按容量劃分為大小相等的兩塊俄周,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了髓迎,就將還存活著的對象復(fù)制到另外一塊上面峦朗,然后再把已使用過的內(nèi)存空間一次清理掉。
缺點(diǎn):
這種算法的代價(jià)是將內(nèi)存縮小為了原來的一半排龄,還要來回移動(dòng)數(shù)據(jù)波势。
3.3、標(biāo)記整理
原理:
首先標(biāo)記出所有需要回收的對象橄维,在標(biāo)記完成后尺铣,后續(xù)步驟是讓所有存活的對象都向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存争舞。
缺點(diǎn):
涉及到移動(dòng)大量對象凛忿,效率不高。
總結(jié):
3.4 竞川、三色標(biāo)記跟讀寫屏障
前面說的三種回收算法都說到了先 標(biāo)記 店溢,問題是如何標(biāo)記的呢? **說話說一半流译,小心沒老伴 **逞怨!
接下來的知識點(diǎn)個(gè)人感覺面試應(yīng)該問不到那么深了,但是為了 必須Mark下 福澡! CMS 叠赦、 G1標(biāo)記時(shí)候一般用的是 三色標(biāo)記法 ,根據(jù)可達(dá)性分析從GC Roots開始進(jìn)行遍歷訪問,可達(dá)的則為存活對象除秀,而最終不可達(dá)說明就是需要被GC對象斥扛。大致流程是把遍歷對象圖過程中遇到的對象,按 是否訪問過 這個(gè)條件標(biāo)記成以下三種顏色:
白色:尚未訪問過规揪。
黑色:本對象已訪問過度苔,而且本對象 引用到 的其他對象 也全部訪問過了。
灰色:本對象已訪問過暂吉,但是本對象 引用到 的其他對象 尚未全部訪問完 胖秒。全部訪問后會(huì)轉(zhuǎn)換為黑色。
假設(shè)現(xiàn)在有白慕的、灰阎肝、黑三個(gè)集合(表示當(dāng)前對象的顏色),遍歷訪問過程:
1肮街、初始時(shí)所有對象都在白色集合中风题。
2、將GC Roots 直接引用到的對象挪到灰色集合中嫉父。
3沛硅、從灰色集合中獲取對象:第一步將本對象 引用到的 其他對象 全部挪到灰色集合中,第二步將本對象 挪到黑色集合里面绕辖。
4摇肌、重復(fù)步驟3,直至灰色集合為空時(shí)結(jié)束引镊。
5朦蕴、結(jié)束后仍在白色集合的對象即為GC Roots 不可達(dá) ,可以嘗試進(jìn)行回收弟头。
當(dāng)STW時(shí)對象間的引用是不會(huì)發(fā)生變化的吩抓,可以輕松完成標(biāo)記。當(dāng)支持并發(fā)標(biāo)記時(shí)赴恨,對象間的引用可能發(fā)生變化疹娶,多標(biāo)和漏標(biāo)的情況就有可能發(fā)生。
3.4 .1伦连、浮動(dòng)垃圾
狀況:GC線程遍歷到E(E是灰色)雨饺,一個(gè)業(yè)務(wù)線程執(zhí)行了D.E = null,此時(shí)E應(yīng)該被回收的惑淳。但是GC線程已經(jīng)認(rèn)為E是灰色了會(huì)繼續(xù)遍歷额港,導(dǎo)致E沒有被回收。
3.4 .2歧焦、漏標(biāo)
GC線程遍歷到E(灰色了)移斩。業(yè)務(wù)線程執(zhí)行了E–>G斷開,D–>G鏈接的操作。GC線程發(fā)現(xiàn)E無法到達(dá)G向瓷,因?yàn)槭呛谏粫?huì)再遍歷標(biāo)記了肠套。最終導(dǎo)致漏標(biāo)G。
漏標(biāo)的必備兩個(gè)條件: 灰到白斷開 猖任, 黑到白建立 你稚。
漏標(biāo)解決方法:
將對象G存儲到特定集合中,等并發(fā)標(biāo)記遍歷完畢后再對集合中對象進(jìn)行 重新標(biāo)記 朱躺。
3.4.2.1刁赖、CMS方案
這里比如開始B指向C,但是后來B不指向C长搀,A指向D乾闰,最簡單的方法是 將A變成灰色 ,等待下次進(jìn)行再次遍歷盈滴。
CMS中可能引發(fā) ABA 問題:
1、回收線程 m1 正在標(biāo)記A轿钠,屬性A.1標(biāo)記完畢巢钓,正在標(biāo)記屬性A.2。
2疗垛、業(yè)務(wù)線程 m2 把屬性1指向了C症汹,由于CMS方案此時(shí)回收線程 m3 把A標(biāo)記位灰色。
3贷腕、回收線程 m1 認(rèn)為所有屬性標(biāo)記完畢背镇,將A設(shè)置為黑色,結(jié)果C漏標(biāo)泽裳。所以CMS階段需要重新標(biāo)記瞒斩。
3.4.2.2、讀寫屏障
漏標(biāo)的實(shí)現(xiàn)是有三步的涮总,JVM加入了讀寫屏障胸囱,其中讀屏障則是攔截第一步,寫屏障用于攔截第二和第三步瀑梗。
寫屏障 + SATB(原始快照) 來破壞 灰到白斷開烹笔。
寫屏障 + 增量更新 來破壞 黑到白建立。
讀屏障 一種保守方式來破壞灰到白斷開后白的存儲抛丽,此時(shí)用讀屏障OK的谤职。
現(xiàn)代使用可達(dá)性分析的垃圾回收器幾乎都借鑒了三色標(biāo)記的算法思想,盡管實(shí)現(xiàn)的方式不盡相同亿鲜。對于讀寫屏障允蜈,以Java HotSpot VM為例,其 并發(fā)標(biāo)記時(shí)對漏標(biāo) 的處理方案如下:
CMS : 寫屏障 + 增量更新
G1 : 寫屏障 + SATB
ZGC : 讀屏障
CMS中使用的增量更新,在重新標(biāo)記階段除了需要遍歷 寫屏障的記錄陷寝,還 需要重新掃描遍歷GC Roots(標(biāo)記過的不用再標(biāo)記)锅很,這是由于CMS對于astore_x等指令不添加寫屏障的原因。
4凤跑、GC流程
核心思想就是 根據(jù)各個(gè)年代的特點(diǎn)不同選用不同到垃圾收集算法 爆安。
年輕代 :使用 復(fù)制算法
老年代 : 使用 標(biāo)記整理 或者 標(biāo)記清除 算法。
為什么要有年輕代:
分代的好處就是 優(yōu)化GC性能 仔引,如果沒有分代每次掃描所有區(qū)域能累死GC扔仓。因?yàn)楹芏鄬ο髱缀蹙褪?朝生夕死 的,如果分代的話咖耘,我們把新創(chuàng)建的對象放到某一地方翘簇,當(dāng)GC的時(shí)候先把這塊存 朝生夕死 (80%以上)對象的區(qū)域進(jìn)行回收,這樣就會(huì)騰出很大的空間出來儿倒。
4.1版保、 年輕代
HotSpot JVM把年輕代分為了三部分:1個(gè) Eden 區(qū)和2個(gè) Survivor 區(qū)(分別叫 from 和 to )。默認(rèn)比例為 8:1:1 夫否。一般情況下彻犁,新創(chuàng)建的對象都會(huì)被分配到 Eden 區(qū)(一些大對象特殊處理),這些對象經(jīng)過第一次Minor GC后凰慈,如果仍然存活汞幢,將會(huì)被移到 Survivor 區(qū)。對象在Survivor區(qū)中每熬過一次 Minor GC 年齡就會(huì)增加1歲微谓,當(dāng)它的年齡增加到一定次數(shù)(默認(rèn) 15 次)時(shí)森篷,就會(huì)被移動(dòng)到年老代中。年輕代的垃圾回收算法使用的是 復(fù)制算法 豺型。
年輕代GC過程:
GC開始前仲智,年輕代對象只會(huì)存在于 Eden 區(qū)和名為 From 的 Survivor 區(qū),名為 To 的 Survivor 區(qū)永遠(yuǎn)是空的姻氨。如果新分配對象在 Eden 申請空間發(fā)現(xiàn)不足就會(huì)導(dǎo)致GC坎藐。
yang GC : Eden 區(qū)中所有存活的對象都會(huì)被復(fù)制到 To ,而在 From 區(qū)中哼绑,仍存活的對象會(huì)根據(jù)他們的年齡值來決定去向岩馍。年齡達(dá)到一定值(年齡閾值可以通過 -XX:MaxTenuringThreshold來設(shè)置)的對象會(huì)被移動(dòng)到年老代中,沒有達(dá)到閾值的對象會(huì)被復(fù)制到 To 區(qū)域抖韩。經(jīng)過這次GC后蛀恩, Eden 區(qū)和 From 區(qū)已經(jīng)被清空。這個(gè)時(shí)候茂浮, From 和 To 會(huì)交換他們的角色双谆,也就是新的 To 就是上次GC前的 From 壳咕,新的 From 就是上次GC前的 To 。不管怎樣 都會(huì)保證名為To的Survivor區(qū)域是空的 顽馋。 Minor GC 會(huì)一直重復(fù)這樣的過程谓厘,直到 To 區(qū)被填滿, To 區(qū)被填滿之后寸谜,會(huì)將所有對象移動(dòng)到年老代中竟稳。這里注意如果yang GC 后空間還是不夠用則會(huì) **空間擔(dān)保 **機(jī)制將數(shù)據(jù)送到Old區(qū)
卡表 Card Table:
為了支持高頻率的新生代回收 ,虛擬機(jī)使用一種叫做 卡表 (Card Table)的數(shù)據(jù)結(jié)構(gòu)熊痴,卡表作為一個(gè)比特位的集合他爸, 每一個(gè)比特位可以用來表示年老代的某一區(qū)域中的所有對象是否持有新生代對象的引用 。
新生代GC時(shí)不用花大量的時(shí)間掃描所有年老代對象果善,來確定每一個(gè)對象的引用關(guān)系诊笤, 先掃描卡表 ,只有卡表的標(biāo)記位為1時(shí)巾陕,才需要掃描給定區(qū)域的年老代對象讨跟。而卡表位為0的所在區(qū)域的年老代對象,一定不包含有對新生代的引用鄙煤。
4.2许赃、 老年代
老年代GC過程:
老年代中存放的對象是存活了很久的,年齡大于15的對象 或者 觸發(fā)了老年代的 分配擔(dān)保 機(jī)制存儲的大對象馆类。在老年代觸發(fā)的gc叫 major gc 也叫 full gc 。 full gc會(huì)包含年輕代的gc 弹谁。 full gc 采用的是 標(biāo)記-清除 或 標(biāo)記整理 乾巧。在執(zhí)行 full gc 的情況下,會(huì)阻塞程序的正常運(yùn)行预愤。老年代的gc比年輕代的gc效率上 慢10倍以上 沟于。對效率有很大的影響。所以 一定要盡量避免老年代GC 植康!
4.3旷太、 元空間
永久代的回收會(huì)隨著 full gc 進(jìn)行移動(dòng),消耗性能销睁。每種類型的垃圾回收都需要特殊處理 元數(shù)據(jù)供璧。將元數(shù)據(jù)剝離出來,簡化了垃圾收集冻记,提高了效率睡毒。
-XX:MetaspaceSize 初始空間的大小。達(dá)到該值就會(huì)觸發(fā)垃圾收集進(jìn)行類型卸載冗栗,同時(shí)GC會(huì)對該值進(jìn)行調(diào)整:
如果釋放了大量的空間演顾,就適當(dāng)降低該值供搀;
如果釋放了很少的空間,那么在不超過MaxMetaspaceSize時(shí)钠至,適當(dāng)提高該值葛虐。
-XX:MaxMetaspaceSize:
最大空間,默認(rèn)是沒有限制的棉钧。
4.4 屿脐、垃圾回收流程總結(jié)
大致的 GC回收流程 如 上圖 ,還有一種設(shè)置就是 大對象直接進(jìn)入老年代 :
如果在新生代分配失敗且對象是一個(gè)不含任何對象引用的大數(shù)組掰盘,可被直接分配到老年代摄悯。通過在老年代的分配避免新生代的一次垃圾回收。
設(shè)置了-XX:PretenureSizeThreshold 值愧捕,任何比這個(gè)值大的對象都不會(huì)嘗試在新生代分配奢驯,將在老年代分配內(nèi)存。
內(nèi)存回收跟分配策略
優(yōu)先在Eden上分配對象次绘,此區(qū)域垃圾回收頻繁速度還快瘪阁。
大對象直接進(jìn)入老生代。
年長者(長期存活對象默認(rèn)15次)跟 進(jìn)入老生代邮偎。
在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半管跺,年齡大于或等于該年齡的對象會(huì)群體進(jìn)入老生代。
空間分配擔(dān)保(擔(dān)保minorGC)禾进,如果Minor GC后 Survivor區(qū)放不下新生代仍存活的對象豁跑,把Suvivor 無法容納的對象直接進(jìn)入老年代。
5泻云、垃圾收集器
5.1艇拍、 垃圾收集器
堆heap是垃圾回收機(jī)制的重點(diǎn)區(qū)域。我們知道垃圾回收機(jī)制有三種 minor gc 宠纯、 major gc 和 full g c卸夕。針對于堆的就是前兩種。年輕代的叫 minor gc 婆瓜,老年代的叫 major gc 快集。
JDK7、JDK8 默認(rèn)垃圾收集器 Parallel Scavenge(新生代)+ Parallel Old(老年代)
JDK9 默認(rèn)垃圾收集器 G1 廉白,服務(wù)端開發(fā)常見組合就是 ParNew + CMS
工程化使用的時(shí)候使用指定的垃圾收集器組合使用个初,講解垃圾收集器前先普及幾個(gè)重要知識點(diǎn):
STW
java中 Stop-The-World 機(jī)制簡稱STW,是指執(zhí)行垃圾收集算法時(shí)Java應(yīng)用程序的 其他所有線程都被掛起 (除了垃圾收集幫助器之外)猴蹂。是Java中一種全局暫筒颍現(xiàn)象,全局停頓晕讲,所有Java代碼停止覆获,native代碼雖然可以執(zhí)行但不能與JVM交互马澈,如果發(fā)生了STW 現(xiàn)象多半是由于gc引起 。
吞吐量
吞吐量 = 運(yùn)行用戶代碼時(shí)間 / ( 運(yùn)行用戶代碼時(shí)間 + 垃圾收集時(shí)間 )弄息。例如:虛擬機(jī)共運(yùn)行100分鐘痊班,垃圾收集器花掉1分鐘,那么吞吐量就是99%
垃圾收集時(shí)間
垃圾回收頻率 * 單次垃圾回收時(shí)間
并行收集
指多條垃圾收集線程并行工作摹量,但此時(shí)用戶線程仍 處于等待狀態(tài) 涤伐。
并發(fā)收集
指 用戶線程與垃圾收集線程同時(shí)工作 (不一定是并行的可能會(huì)交替執(zhí)行)。用戶程序在繼續(xù)運(yùn)行缨称,而垃圾收集程序運(yùn)行在另一個(gè)CPU上凝果。
5.2、 新生代
新生代有 Serial 睦尽、 ParNew 器净、 Parallel Scavenge 三種垃圾收集器。
5.3当凡、 老年代
老年代有 Serial Old 山害、 Parallel Old 、 CMS 三種垃圾收集器沿量。
5.3.1浪慌、CMS
CMS (Concurrent Mark Sweep)比較重要這里 重點(diǎn)說一下 。
CMS的初衷和目的:
為了消除Throught收集器和Serial收集器在Full GC周期中的長時(shí)間停頓朴则。是一種 以獲取最短回收停頓時(shí)間為目標(biāo) 的收集器权纤,具有自適應(yīng)調(diào)整策略,適合互聯(lián)網(wǎng)站 跟B/S 服務(wù)應(yīng)用乌妒。
CMS的適用場景:
如果你的應(yīng)用需要 更快的響應(yīng) 汹想,不希望有長時(shí)間的停頓,同時(shí)你的 CPU資源也比較豐富 芥被,就適合使用CMS收集器。比如常見的Server端任務(wù)坐榆。
優(yōu)點(diǎn):
并發(fā)收集拴魄、低停頓。
缺點(diǎn):
CMS收集器對CPU資源非常敏感 :在并發(fā)階段席镀,雖然不會(huì)導(dǎo)致用戶線程停頓匹中,但是會(huì)占用CPU資源而導(dǎo)致引用程序變慢,總吞吐量下降豪诲。
無法處理浮動(dòng)垃圾 :由于CMS并發(fā)清理階段用戶線程還在運(yùn)行顶捷,伴隨程序的運(yùn)行自然會(huì)有新的垃圾不斷產(chǎn)生,這一部分垃圾出現(xiàn)在標(biāo)記過程之后屎篱,CMS無法在本次收集中處理它們服赎,只好留待下一次GC時(shí)將其清理掉葵蒂。這一部分垃圾稱為 浮動(dòng)垃圾 。 如果內(nèi)存放不下浮動(dòng)垃圾這時(shí) JVM 啟動(dòng) Serial Old 替代 CMS 重虑。
空間碎片 :CMS是基于 標(biāo)記-清除 算法實(shí)現(xiàn)的收集器践付,使用 標(biāo)記-清除 算法收集后,會(huì)產(chǎn)生 大量碎片 缺厉。
CMS回收流程:
初始標(biāo)記 : 引發(fā)STW 永高, 僅僅只是標(biāo)記出GC ROOTS能直接關(guān)聯(lián)到的對象,速度很快提针。
并發(fā)標(biāo)記 : 不引發(fā)STW 命爬,正常運(yùn)行 所有Old 對象是否可鏈到GC Roots
重新標(biāo)記 : 引發(fā)STW ,為了修正并發(fā)標(biāo)記期間辐脖,因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生改變的標(biāo)記饲宛。這個(gè)階段的停頓時(shí)間會(huì)被初始標(biāo)記階段稍長,但比并發(fā)標(biāo)記階段要短揖曾。
并發(fā)清除 : 不引發(fā)STW 落萎,正常運(yùn)行,標(biāo)記清除算法來清理刪除掉標(biāo)記階段判斷的已經(jīng)死亡的對象炭剪。
總結(jié):
并發(fā)標(biāo)記 和 并發(fā)清除 的耗時(shí)最長但是不需要停止用戶線程练链。 初始標(biāo)記 和 重新標(biāo)記 的耗時(shí)較短,但是需要停止用戶線程奴拦,所以整個(gè)GC過程造成的停頓時(shí)間較短媒鼓,大部分時(shí)候是可以和用戶線程一起工作的。
之前的GC收集器對Heap的劃分:
以前垃圾回收器是 新生代 + 老年代 错妖,用了CMS效果也不是很好绿鸣,為了減少STW對系統(tǒng)的影響引入了G1(Garbage-First Garbage Collector), G1 是一款面向服務(wù)端應(yīng)用的垃圾收集器暂氯,具有如下特點(diǎn):
1潮模、 并行與并發(fā) :G1能充分利用多CPU、多核環(huán)境下的硬件優(yōu)勢痴施,可以通過并發(fā)的方式讓Java程序繼續(xù)執(zhí)行擎厢。
2、 分代收集 :分代概念在G1中依然得以保留辣吃,它能夠采用不同的方式去處理新創(chuàng)建的對象和已經(jīng)存活了一段時(shí)間动遭、熬過多次GC的舊對象來獲得更好的收集效果。
3神得、 空間整合 :G1從整體上看是基于 標(biāo)記-整理 算法實(shí)現(xiàn)的厘惦,從局部(兩個(gè)Region之間)上看是基于 復(fù)制算法 實(shí)現(xiàn)的,G1運(yùn)行期間不會(huì)產(chǎn)生內(nèi)存空間碎片哩簿。
4宵蕉、 可預(yù)測停頓 :G1比CMS牛在能建立可預(yù)測的停頓時(shí)間模型酝静,能讓使用者明確指定在一個(gè)長度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不得超過N毫秒国裳。
G1作為JDK9之后的服務(wù)端默認(rèn)收集器形入,不再區(qū)分年輕代和老年代進(jìn)行垃圾回收,G1默認(rèn)把堆內(nèi)存分為N個(gè)分區(qū)缝左,每個(gè)1~32M(總是2的冪次方)亿遂。并且提供了四種不同Region標(biāo)簽 Eden 、 Survivor 渺杉、 Old 蛇数、 Humongous 。H區(qū)可以認(rèn)為是Old區(qū)中一種特別專門用來存儲大數(shù)據(jù)的是越,關(guān)于H區(qū)數(shù)據(jù)存儲類型一般符合下面條件:
當(dāng) 0.5 Region <= 當(dāng)對象大小 <= 1 Region 時(shí)候?qū)?shù)據(jù)存儲到 H區(qū)
當(dāng)對象大小 > 1 Region 存儲到連續(xù)的H區(qū)耳舅。
同時(shí)G1中引入了 RememberSets 、 CollectionSets 幫助更好的執(zhí)行GC 倚评。
1浦徊、 RememberSets : RSet 記錄了其他Region中的對象引用本Region中對象的關(guān)系,屬于points-into結(jié)構(gòu)(誰引用了我的對象)
2天梧、 CollectionSets : Csets 是一次GC中需要被清理的regions集合盔性,注意G1每次GC不是全部region都參與的,可能只清理少數(shù)幾個(gè)呢岗,這幾個(gè)就被叫做Csets冕香。在GC的時(shí)候,對于old -> young 和old -> old的跨代對象引用后豫,只要掃描對應(yīng)的 CSet 中的 RSet 即可悉尾。
G1進(jìn)行GC的時(shí)候一般分為 Yang GC 跟 Mixed GC 。
Young GC : CSet 就是所有年輕代里面的Region
Mixed GC : CSet 是所有年輕代里的Region加上在全局并發(fā)標(biāo)記階段標(biāo)記出來的收益高的Region
5.4.1挫酿、Yang GC
標(biāo)準(zhǔn)的年輕代GC算法构眯,整體思路跟CMS中類似。
5.4.2早龟、Mixed GC
G1中是 沒 有Old GC的惫霸,有一個(gè)把老年代跟新生代同時(shí)GC的 Mixed GC,它的 回收流程 :
1拄衰、 初始標(biāo)記 : 是STW事件 它褪,其完成工作是標(biāo)記GC ROOTS 直接可達(dá)的對象饵骨。標(biāo)記位RootRegion翘悉。
2、 根區(qū)域掃描 : 不是STW事件 居触,拿來RootRegion妖混,掃描整個(gè)Old區(qū)所有Region老赤,看每個(gè)Region的 Rset 中是否有RootRegion。有則標(biāo)識出來制市。
3抬旺、 并發(fā)標(biāo)記 : 同CMS并發(fā)標(biāo)記 不需要STW ,遍歷范圍減少祥楣,在此只需要遍歷 第二步 被標(biāo)記到引用老年代的對象 RSet开财。
4、 最終標(biāo)記 : 同 CMS 重新標(biāo)記 會(huì)STW 误褪,用的 SATB 操作责鳍,速度更快。
5兽间、 清除 : STW操作 历葛,用 復(fù)制清理算法 ,清點(diǎn)出有存活對象的Region和沒有存活對象的Region(Empty Region)嘀略,更新Rset恤溶。把Empty Region收集起來到可分配Region隊(duì)列。
回收總結(jié):
1帜羊、經(jīng)過global concurrent marking咒程,collector就知道哪些Region有存活的對象。并將那些完全可回收的Region(沒有存活對象)收集起來加入到可分配Region隊(duì)列逮壁,實(shí)現(xiàn)對該部分內(nèi)存的回收孵坚。對于有存活對象的Region,G1會(huì)根據(jù)統(tǒng)計(jì)模型找出收益最高窥淆、開銷不超過用戶指定的上限的若干Region進(jìn)行對象回收卖宠。這些選中被回收的Region組成的集合就叫做collection set 簡稱Cset!
2忧饭、在MIX GC中的Cset = 所有年輕代里的region + 根據(jù)global concurrent marking統(tǒng)計(jì)得出收集收益高的若干old region 扛伍。
3、在YGC中的Cset = 所有年輕代里的region + 通過控制年輕代的region個(gè)數(shù)來控制young GC的開銷 词裤。
4刺洒、YGC 與 MIXGC 都是采用多線程復(fù)制清理,整個(gè)過程會(huì)STW吼砂。 G1的 低延遲原理 在于其回收的區(qū)域變得精確并且范圍變小了逆航。
G1提速點(diǎn):
1 重新標(biāo)記 使X區(qū)域直接刪除。
2 Rset 降低了掃描的范圍渔肩,上題中兩點(diǎn)巢株。
3 重新標(biāo)記階段使用 SATB 速度比CMS快脱盲。
4 清理過程為選取部分存活率低的Region進(jìn)行清理恃鞋,不是全部贴唇,提高了清理的效率。
總結(jié):
就像你把房子打掃干凈,你可能只把顯眼而比較大的垃圾打掃了,犄角旮旯的你沒打掃。
一句話總結(jié)G1思維: 每次選擇性的清理大部分垃圾來保證時(shí)效性跟系統(tǒng)的正常運(yùn)行 胡嘿。