Java垃圾回收器和內(nèi)存分配回收策略

1.GC的分析

  • Where/Which徙缴?
    首先需要確定堆冒黑、元數(shù)據(jù)空間(Metaspace)等共享區(qū)域哪些內(nèi)存可以回收田绑。棧等私有數(shù)據(jù)隨著棧而消亡。
  • When抡爹?
    GC什么時(shí)候回收掩驱。
  • How?
    GC怎樣回收對(duì)程序的影響是最小的冬竟,是最高效的欧穴。
  • 了解GC和內(nèi)存分配的意義
    GC對(duì)應(yīng)于的性能是有影響的;
    能夠?qū)懗龈玫拇a泵殴。

2.判斷對(duì)象的存活

2.1 引用計(jì)數(shù)法

快涮帘,方便,實(shí)現(xiàn)簡(jiǎn)單笑诅;
缺點(diǎn):對(duì)象相互引用時(shí)调缨,很難判斷對(duì)象是否改回收。

2.2 可達(dá)性分析

來判定對(duì)象是否存活的吆你。這個(gè)算法的基本思路就是通過一系列的稱為“GC Roots”的對(duì)象作為起始點(diǎn)弦叶,從這些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain)妇多,當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連時(shí)伤哺,則證明此對(duì)象是不可用的。

作為GC Roots的對(duì)象包括下面幾種:

  • 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象砌梆。
  • 方法區(qū)中類靜態(tài)屬性引用的對(duì)象默责。
  • 方法區(qū)中常量引用的對(duì)象。
  • 本地方法棧中JNI(即一般說的Native方法)引用的對(duì)象咸包。

2.3 各種引用

  • 1)強(qiáng)引用
    一般的Object obj = new Object() 桃序,就屬于強(qiáng)引用。

  • 2)軟引用 SoftReference
    一些有用但是并非必需烂瘫,用軟引用關(guān)聯(lián)的對(duì)象媒熊,系統(tǒng)將要發(fā)生OOM之前奇适,這些對(duì)象就會(huì)被回收。參見代碼:
    軟引用測(cè)試代碼TestSoftRef.java

-Xms10m -Xmx10m -Xlog:gc
[0.015s][info][gc] Using G1
User [id=1, name=Mark]
[0.176s][info][gc] GC(0) Pause Full (System.gc()) 3M->1M(10M) 3.365ms
AfterGc
User [id=1, name=Mark]
********************User [id=1, name=Mark]
[0.187s][info][gc] GC(1) Pause Initial Mark (G1 Humongous Allocation) 1M->1M(10M) 1.958ms
[0.187s][info][gc] GC(2) Concurrent Cycle
********************User [id=1, name=Mark]
********************User [id=1, name=Mark]
[0.192s][info][gc] GC(3) To-space exhausted
[0.193s][info][gc] GC(3) Pause Young (G1 Humongous Allocation) 5M->5M(10M) 2.592ms
[0.195s][info][gc] GC(4) Pause Full (Allocation Failure) 5M->5M(10M) 2.260ms
[0.195s][info][gc] GC(5) Pause Young (G1 Evacuation Pause) 7M->7M(10M) 0.197ms
[0.196s][info][gc] GC(6) Pause Young (G1 Evacuation Pause) 7M->7M(10M) 0.159ms
[0.196s][info][gc] GC(2) Concurrent Cycle 8.788ms
[0.199s][info][gc] GC(7) Pause Full (Allocation Failure) 7M->7M(10M) 3.491ms
********************User [id=1, name=Mark]
[0.200s][info][gc] GC(8) Pause Initial Mark (G1 Humongous Allocation) 7M->7M(10M) 0.198ms
[0.200s][info][gc] GC(9) Concurrent Cycle
[0.200s][info][gc] GC(10) Pause Young (G1 Humongous Allocation) 7M->7M(10M) 0.109ms
[0.202s][info][gc] GC(11) Pause Full (Allocation Failure) 7M->7M(10M) 2.552ms
[0.202s][info][gc] GC(9) Concurrent Cycle 2.851ms
[0.203s][info][gc] GC(12) Pause Young (G1 Evacuation Pause) 9M->9M(10M) 0.173ms
[0.203s][info][gc] GC(13) Pause Initial Mark (G1 Evacuation Pause) 9M->9M(10M) 0.131ms
[0.203s][info][gc] GC(14) Concurrent Cycle
[0.206s][info][gc] GC(15) Pause Full (Allocation Failure) 9M->9M(10M) 3.057ms
[0.210s][info][gc] GC(16) Pause Full (Allocation Failure) 9M->9M(10M) 3.467ms
[0.210s][info][gc] GC(14) Concurrent Cycle 6.733ms
[0.210s][info][gc] GC(17) Pause Young (G1 Evacuation Pause) 9M->7M(10M) 0.193ms
Throwable********************null

Process finished with exit code 0
  • 3)弱引用 WeakReference
    一些有用(程度比軟引用更低)但是并非必需芦鳍,用弱引用關(guān)聯(lián)的對(duì)象嚷往,只能生存到下一次垃圾回收之前,GC發(fā)生時(shí)柠衅,不管內(nèi)存夠不夠皮仁,都會(huì)被回收。
    弱引用測(cè)試代碼TestWeakRef.java
User [id=1, name=Mark]
AfterGc
null
  • 4)虛引用 PhantomReference
    幽靈引用菲宴,最弱贷祈,被垃圾回收的時(shí)候收到一個(gè)通知

注意:軟引用 SoftReference和弱引用 WeakReference,可以用在內(nèi)存資源緊張的情況下以及創(chuàng)建不是很重要的數(shù)據(jù)緩存喝峦。當(dāng)系統(tǒng)內(nèi)存不足的時(shí)候势誊,緩存中的內(nèi)容是可以被釋放的。

例如谣蠢,一個(gè)程序用來處理用戶提供的圖片粟耻。如果將所有圖片讀入內(nèi)存,這樣雖然可以很快的打開圖片眉踱,但內(nèi)存空間使用巨大挤忙,一些使用較少的圖片浪費(fèi)內(nèi)存空間,需要手動(dòng)從內(nèi)存中移除勋锤。如果每次打開圖片都從磁盤文件中讀取到內(nèi)存再顯示出來饭玲,雖然內(nèi)存占用較少,但一些經(jīng)常使用的圖片每次打開都要訪問磁盤叁执,代價(jià)巨大茄厘。這個(gè)時(shí)候就可以用軟引用構(gòu)建緩存。

3.垃圾回收算法

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

算法分為“標(biāo)記”和“清除”兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象谈宛,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象次哈。
它的主要不足空間問題,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片吆录,空間碎片太多可能會(huì)導(dǎo)致以后在程序運(yùn)行過程中需要分配較大對(duì)象時(shí)窑滞,無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作。


3.2 復(fù)制算法(Copying)

將可用內(nèi)存按容量劃分為大小相等的兩塊恢筝,每次只使用其中的一塊哀卫。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另外一塊上面撬槽,然后再把已使用過的內(nèi)存空間一次清理掉此改。這樣使得每次都是對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時(shí)也就不用考慮內(nèi)存碎片等復(fù)雜情況侄柔,只要按順序分配內(nèi)存即可共啃,實(shí)現(xiàn)簡(jiǎn)單占调,運(yùn)行高效。只是這種算法的代價(jià)是將內(nèi)存縮小為了原
來的一半移剪。


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

首先標(biāo)記出所有需要回收的對(duì)象究珊,在標(biāo)記完成后,后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理纵苛,而是讓所有存活的對(duì)象都向一端移動(dòng)剿涮,然后直接清理掉端邊界以外的內(nèi)存。


4.具體的垃圾回收器

查看垃圾回收器:

java -XX:+PringCommandLineFlags -version

4.1 所有的算法都用上

當(dāng)前商業(yè)虛擬機(jī)的垃圾收集都采用“分代收集”(Generational Collection)算法攻人,這種算法并沒有什么新的思想幔虏,只是根據(jù)對(duì)象存活周期的不同將內(nèi)存劃分為幾塊。一般是把Java堆分為新生代和老年代贝椿,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ā?br> 專門研究表明,新生代中的對(duì)象98%是“朝生夕死”的陷谱,所以并不需要按照1:1的比例來劃分內(nèi)存空間烙博,而是將內(nèi)存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor烟逊。當(dāng)回收時(shí)渣窜,將Eden和Survivor中還存活著的對(duì)象一次性地復(fù)制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過的Survivor空間宪躯。HotSpot虛擬機(jī)默認(rèn)Eden和Survivor的大小比例是8:1乔宿,也就是每次新生代中可用內(nèi)存空間為整個(gè)新生代容量的90%(80%+10%),只有10%的內(nèi)存會(huì)被“浪費(fèi)”访雪。當(dāng)然详瑞,98%的對(duì)象可回收只是一般場(chǎng)景下的數(shù)據(jù),我們沒有辦法保證每次回收都只有不多于10%的對(duì)象存活臣缀,當(dāng)Survivor空間不夠用時(shí)坝橡,需要依賴其他內(nèi)存(這里指老年代)進(jìn)行分配擔(dān)保(Handle Promotion)。
在新生代中精置,每次垃圾收集時(shí)都發(fā)現(xiàn)有大批對(duì)象死去计寇,只有少量存活,那就選用復(fù)制算法脂倦,只需要付出少量存活對(duì)象的復(fù)制成本就可以完成收集番宁。而老年代中因?yàn)閷?duì)象存活率高、沒有額外空間對(duì)它進(jìn)行分配擔(dān)保赖阻,就必須使用“標(biāo)記—清理”或者“標(biāo)記—整理”算法來進(jìn)行回收蝶押。

4.2 垃圾回收器



  • 并行:垃圾收集的多線程的同時(shí)進(jìn)行。
    并發(fā):垃圾收集的多線程和應(yīng)用的多線程同時(shí)進(jìn)行政供。

4.2.1 Serial/Serial Old

最古老的播聪,單線程朽基,獨(dú)占式,成熟离陶,適合單CPU 服務(wù)器
-XX:+UseSerialGC 新生代和老年代都用串行收集器

-XX:+UseParNewGC 新生代使用ParNew稼虎,老年代使用Serial Old

-XX:+UseParallelGC 新生代使用ParallerGC,老年代使用Serial Old

4.2.2 ParNew

和Serial基本沒區(qū)別招刨,唯一的區(qū)別:多線程霎俩,多CPU的,停頓時(shí)間比Serial少
-XX:+UseParNewGC 新生代使用ParNew沉眶,老年代使用Serial Old


4.2.3 Parallel Scavenge(ParallerGC)/Parallel Old

關(guān)注吞吐量的垃圾收集器打却,高吞吐量則可以高效率地利用CPU時(shí)間,盡快完成程序的運(yùn)算任務(wù)谎倔,主要適合在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)柳击。
所謂吞吐量就是CPU用于運(yùn)行用戶代碼的時(shí)間與CPU總消耗時(shí)間的比值,即吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間)片习,虛擬機(jī)總共運(yùn)行了100分鐘捌肴,其中垃圾收集花掉1分鐘,那吞吐量就是99%藕咏。

-XX:+ UseParallelOldGC:新生代使用ParallerGC状知,老年代使用Parallel Old

-XX:MaxGCPauseMills :參數(shù)允許的值是一個(gè)大于0的毫秒數(shù),收集器將盡可能地保證內(nèi)存回收花費(fèi)的時(shí)間不超過設(shè)定值孽查。不過大家不要認(rèn)為如果把這個(gè)參數(shù)的值設(shè)置得稍小一點(diǎn)就能使得系統(tǒng)的垃圾收集速度變得更快饥悴,GC停頓時(shí)間縮短是以犧牲吞吐量和新生代空間來?yè)Q取的:系統(tǒng)把新生代調(diào)小一些,收集300MB新生代肯定比收集500MB快吧盲再,這也直接導(dǎo)致垃圾收集發(fā)生得更頻繁一些西设,原來10秒收集一次、每次停頓100毫秒答朋,現(xiàn)在變成5秒收集一次济榨、每次停頓70毫秒。停頓時(shí)間的確在下降绿映,但吞吐量也降下來了擒滑。

-XX:GCTimeRatio參數(shù)的值應(yīng)當(dāng)是一個(gè)大于0且小于100的整數(shù),也就是垃圾收集時(shí)間占總時(shí)間的比率叉弦,相當(dāng)于是吞吐量的倒數(shù)丐一。如果把此參數(shù)設(shè)置為19,那允許的最大GC時(shí)間就占總時(shí)間的5%(即1/(1+19))淹冰,默認(rèn)值為99库车,就是允許最大1%(即1/(1+99))的垃圾收集時(shí)間。

-XX:+UseAdaptiveSizePolicy 當(dāng)這個(gè)參數(shù)打開之后樱拴,就不需要手工指定新生代的大心堋(-Xmn)洋满、Eden與Survivor區(qū)的比例(-XX:SurvivorRatio)、晉升老年代對(duì)象年齡(-XX:PretenureSizeThreshold)等細(xì)節(jié)參數(shù)了珍坊,虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息牺勾,動(dòng)態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間或者最大的吞吐量,這種調(diào)節(jié)方式稱為GC自適應(yīng)的調(diào)節(jié)策略阵漏。

如果對(duì)于收集器運(yùn)作原來不太了解驻民,手工優(yōu)化存在困難的時(shí)候,使用Parallel Scavenge收集器配合自適應(yīng)調(diào)節(jié)策略履怯,把內(nèi)存管理的調(diào)優(yōu)任務(wù)交給虛擬機(jī)去完成將是一個(gè)不錯(cuò)的選擇回还。只需要把基本的內(nèi)存數(shù)據(jù)設(shè)置好(如-Xmx設(shè)置最大堆),然后使用MaxGCPauseMillis參數(shù)(更關(guān)注最大停頓時(shí)間)或GCTimeRatio(更關(guān)注吞吐量)參數(shù)給虛擬機(jī)設(shè)立一個(gè)優(yōu)化目標(biāo)叹洲,那具體細(xì)節(jié)參數(shù)的調(diào)節(jié)工作就由虛擬機(jī)完成了柠硕。自適應(yīng)調(diào)節(jié)策略也是Parallel Scavenge收集器與ParNew收集器的一個(gè)重要區(qū)別。

4.2.4 Concurrent Mark Sweep (CMS)

收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器运提。目前很大一部分的Java應(yīng)用集中在互聯(lián)網(wǎng)站或者B/S系統(tǒng)的服務(wù)端上仅叫,這類應(yīng)用尤其重視服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時(shí)間最短糙捺,以給用戶帶來較好的體驗(yàn)。CMS收集器就非常符合這類應(yīng)用的需求笙隙。

從名字(包含“Mark Sweep”)上就可以看出洪灯,CMS收集器是基于“標(biāo)記—清除”算法實(shí)現(xiàn)的,它的運(yùn)作過程相對(duì)于前面幾種收集器來說更復(fù)雜一些竟痰,整個(gè)過程分為4個(gè)步驟签钩,包括:

  • 初始標(biāo)記-短暫,僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象坏快,速度很快铅檩。
  • 并發(fā)標(biāo)記-和用戶的應(yīng)用程序同時(shí)進(jìn)行,進(jìn)行GC RootsTracing的過程
  • 重新標(biāo)記-短暫莽鸿,為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄昧旨,這個(gè)階段的停頓時(shí)間一般會(huì)比初始標(biāo)記階段稍長(zhǎng)一些,但遠(yuǎn)比并發(fā)標(biāo)記的時(shí)間短祥得。
  • 并發(fā)清除
    由于整個(gè)過程中耗時(shí)最長(zhǎng)的并發(fā)標(biāo)記和并發(fā)清除過程收集器線程都可以與用戶線程一起工作兔沃,所以,從總體上來說级及,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的乒疏。

-XX:+UseConcMarkSweepGC ,表示新生代使用ParNew饮焦,老年代的用CMS

浮動(dòng)垃圾:由于CMS并發(fā)清理階段用戶線程還在運(yùn)行著怕吴,伴隨程序運(yùn)行自然就還會(huì)有新的垃圾不斷產(chǎn)生窍侧,這一部分垃圾出現(xiàn)在標(biāo)記過程之后,CMS無法在當(dāng)次收集中處理掉它們转绷,只好留待下一次GC時(shí)再清理掉伟件。這一部分垃圾就稱為“浮動(dòng)垃圾”。
同時(shí)用戶的線程還在運(yùn)行暇咆,需要給用戶線程留下運(yùn)行的內(nèi)存空間锋爪。

-XX:CMSInitialOccupyFraction ,因?yàn)橐陨蟽牲c(diǎn)爸业,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進(jìn)行收集其骄,需要預(yù)留一部分空間提供并發(fā)收集時(shí)的程序運(yùn)作使用。在JDK 早期版本的默認(rèn)設(shè)置下扯旷,CMS收集器當(dāng)老年代使用了68%的空間后就會(huì)被激活拯爽,這是一個(gè)偏保守的設(shè)置,如果在應(yīng)用中老年代增長(zhǎng)不是太快钧忽,可以適當(dāng)調(diào)高參數(shù)-XX:CMSInitiatingOccupancyFraction的值來提高觸發(fā)百分比毯炮,以便降低內(nèi)存回收次數(shù)從而獲取更好的性能,在JDK 1.6中耸黑,CMS收集器的啟動(dòng)閾值已經(jīng)提升至92%桃煎。要是CMS運(yùn)行期間預(yù)留的內(nèi)存無法滿足程序需要,就會(huì)出現(xiàn)一次“Concurrent Mode Failure”失敗大刊,這時(shí)虛擬機(jī)將啟動(dòng)后備預(yù)案:臨時(shí)啟用Serial Old收集器來重新進(jìn)行老年代的垃圾收集为迈,這樣停頓時(shí)間就很長(zhǎng)了。所以說參數(shù)-XX:CMSInitiatingOccupancyFraction設(shè)置得太高很容易導(dǎo)致大量“Concurrent Mode Failure”失敗缺菌,性能反而降低葫辐。

-XX:+UseCMSCompactAtFullCollection為了解決這個(gè)問題,CMS收集器提供了一個(gè)這個(gè)開關(guān)參數(shù)(默認(rèn)就是開啟的)伴郁,用于在CMS收集器頂不住要進(jìn)行FullGC時(shí)開啟內(nèi)存碎片的合并整理過程耿战,內(nèi)存整理的過程是無法并發(fā)的,空間碎片問題沒有了焊傅,但停頓時(shí)間不得不變長(zhǎng)剂陡。

-XX:CMSFullGCsBeforeCompaction,這個(gè)參數(shù)是用于設(shè)置執(zhí)行多少次不壓縮的Full GC后狐胎,跟著來一次帶壓縮的(默認(rèn)值為0鹏倘,表示每次進(jìn)入FullGC時(shí)都進(jìn)行碎片整理)。

4.2.5 G1

-XX:+UseG1GC

并行與并發(fā):G1能充分利用多CPU顽爹、多核環(huán)境下的硬件優(yōu)勢(shì)纤泵,使用多個(gè)CPU(CPU或者CPU核心)來縮短Stop-The-World停頓的時(shí)間,部分其他收集器原本需要停頓Java線程執(zhí)行的GC動(dòng)作,G1收集器仍然可以通過并發(fā)的方式讓Java程序繼續(xù)執(zhí)行捏题。
分代收集:與其他收集器一樣玻褪,分代概念在G1中依然得以保留。雖然G1可以不需要其他收集器配合就能獨(dú)立管理整個(gè)GC堆公荧,但它能夠采用不同的方式去處理新創(chuàng)建的對(duì)象和已經(jīng)存活了一段時(shí)間带射、熬過多次GC的舊對(duì)象以獲取更好的收集效果。
空間整合:與CMS的“標(biāo)記—清理”算法不同循狰,G1從整體來看是基于“標(biāo)記—整理”算法實(shí)現(xiàn)的收集器窟社,從局部(兩個(gè)Region之間)上來看是基于“復(fù)制”算法實(shí)現(xiàn)的,但無論如何绪钥,這兩種算法都意味著G1運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存空間碎片,收集后能提供規(guī)整的可用內(nèi)存。這種特性有利于程序長(zhǎng)時(shí)間運(yùn)行见转,分配大對(duì)象時(shí)不會(huì)因?yàn)闊o法找到連續(xù)內(nèi)存空間而提前觸發(fā)下一次GC。
內(nèi)存布局:在G1之前的其他收集器進(jìn)行收集的范圍都是整個(gè)新生代或者老年代,而G1不再是這樣。使用G1收集器時(shí)花竞,Java堆的內(nèi)存布局就與其他收集器有很大差別厌蔽,它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region)逾条,雖然還保留有新生代和老年代的概念泳桦,但新生代和老年代不再是物理隔離的了,它們都是一部分Region(不需要連續(xù))的集合浮毯。

G1收集的幾個(gè)階段:

  • 新生代GC
    回收Eden區(qū)和survivor區(qū)完疫,回收后,所有eden區(qū)被清空债蓝,存在一個(gè)survivor區(qū)保存了部分?jǐn)?shù)據(jù)壳鹤。老年代區(qū)域會(huì)增多,因?yàn)椴糠中律膶?duì)象會(huì)晉升到老年代饰迹。
  • 并發(fā)標(biāo)記周期
    初始標(biāo)記:短暫芳誓,僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度很快啊鸭,產(chǎn)生一個(gè)全局停頓锹淌,都伴隨有一次新生代的GC。
    根區(qū)域掃描:掃描survivor區(qū)可以直接到達(dá)的老年代區(qū)域赠制。
    并發(fā)標(biāo)記階段:掃描和查找整個(gè)堆的存活對(duì)象赂摆,并標(biāo)記。
    重新標(biāo)記:會(huì)產(chǎn)生全局停頓,對(duì)并發(fā)標(biāo)記階段的結(jié)果進(jìn)行修正库正。
    獨(dú)占清理:會(huì)產(chǎn)生全局停頓曲楚,對(duì)GC回收比例進(jìn)行排序,供混合收集階段使用
    并發(fā)清理:識(shí)別并清理完全空閑的區(qū)域褥符,并發(fā)進(jìn)行
  • 混合收集
    對(duì)含有垃圾比例較高的Region進(jìn)行回收龙誊。
  • G1當(dāng)出現(xiàn)內(nèi)存不足的的情況,也可能進(jìn)行的FullGC回收喷楣。

G1中重要的參數(shù):
-XX:MaxGCPauseMillis 指定目標(biāo)的最大停頓時(shí)間趟大,G1嘗試調(diào)整新生代和老年代的比例,堆大小铣焊,晉升年齡來達(dá)到這個(gè)目標(biāo)時(shí)間逊朽。

-XX:ParallerGCThreads:設(shè)置GC的工作線程數(shù)量

4.2.6 未來的垃圾回收

JDK11中的ZGC通過技術(shù)手段把stw的情況控制在僅有一次,就是第一次的初始標(biāo)記才會(huì)發(fā)生曲伊,這樣也就不難理解為什么GC停頓時(shí)間不隨著堆增大而上升了叽讳,再大也是通過并發(fā)的時(shí)間去回收。

關(guān)鍵技術(shù):

  • 1.有色指針(Colored Pointers)
  • 2.加載屏障(Load Barrier)

4.2.7 Stop The World現(xiàn)象

GC收集器和我們GC調(diào)優(yōu)的目標(biāo)就是盡可能的減少STW的時(shí)間和次數(shù)坟募。


測(cè)試代碼請(qǐng)參考StopWorld.java

-Xms300M -Xms300M -XX:+UseSerialGC -Xlog:gc
[0.015s][info][gc] Using Serial
[0.201s][info][gc] GC(0) Pause Young (Allocation Failure) 78M->67M(290M) 34.612ms
[0.254s][info][gc] GC(1) Pause Young (Allocation Failure) 146M->143M(290M) 36.033ms
0.0
[0.314s][info][gc] GC(3) Pause Full (Allocation Failure) 219M->219M(302M) 6.981ms
[0.314s][info][gc] GC(2) Pause Young (Allocation Failure) 222M->219M(511M) 47.486ms
[0.407s][info][gc] GC(4) Pause Young (Allocation Failure) 352M->351M(511M) 69.178ms
0.263
[0.518s][info][gc] GC(6) Pause Full (Allocation Failure) 489M->489M(633M) 13.649ms
[0.520s][info][gc] GC(5) Pause Young (Allocation Failure) 490M->489M(1144M) 85.243ms
0.405
[0.806s][info][gc] GC(7) Pause Young (Allocation Failure) 789M->785M(1144M) 147.244ms
0.661
[1.046s][info][gc] GC(9) Pause Full (Allocation Failure) 1097M->1097M(1415M) 16.483ms
[1.047s][info][gc] GC(8) Pause Young (Allocation Failure) 1100M->1097M(1966M) 202.345ms
0.902
1.4
[1.768s][info][gc] GC(11) Pause Full (Allocation Failure) 1600M->1599M(1966M) 594.880ms
[1.768s][info][gc] GC(10) Pause Young (Allocation Failure) 1600M->1599M(1966M) 594.933ms
1.623

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

請(qǐng)參考Java內(nèi)存區(qū)域 5.5 新生代配置

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

如果說Eden內(nèi)存空間不足岛蚤,就會(huì)發(fā)生Minor GC

5.2 大對(duì)象直接進(jìn)入老年代

大對(duì)象:需要大量連續(xù)內(nèi)存空間的Java對(duì)象,比如很長(zhǎng)的字符串和大型數(shù)組懈糯。

大對(duì)象影響性能的原因涤妒,是因?yàn)樾律玫綇?fù)制算法:
1、導(dǎo)致內(nèi)存有空間赚哗,還是需要提前進(jìn)行垃圾回收獲取連續(xù)空間來放他們她紫;
2、會(huì)進(jìn)行大量的內(nèi)存復(fù)制屿储。

-XX:PretenureSizeThreshold 參數(shù) 贿讹,大于這個(gè)數(shù)量直接在老年代分配,缺省為0 够掠,表示絕不會(huì)直接分配在老年代民褂。

5.3 長(zhǎng)期存活的對(duì)象將進(jìn)入老年代

默認(rèn)15歲,-XX:MaxTenuringThreshold調(diào)整

5.4 動(dòng)態(tài)對(duì)象年齡判定

為了能更好地適應(yīng)不同程序的內(nèi)存狀況祖屏,虛擬機(jī)并不是永遠(yuǎn)地要求對(duì)象的年齡必須達(dá)到了MaxTenuringThreshold才能晉升老年代助赞,如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半买羞,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代袁勺,無須等到MaxTenuringThreshold中要求的年齡

5.5 空間分配擔(dān)保

新生代中有大量的對(duì)象存活,survivor空間不夠畜普,當(dāng)出現(xiàn)大量對(duì)象在MinorGC后仍然存活的情況(最極端的情況就是內(nèi)存回收后新生代中所有對(duì)象都存活)期丰,就需要老年代進(jìn)行分配擔(dān)保,把Survivor無法容納的對(duì)象直接進(jìn)入老年代.只要老年代的連續(xù)空間大于新生代對(duì)象的總大小或者歷次晉升的平均大小,就進(jìn)行Minor GC钝荡,否則FullGC街立。

6.內(nèi)存泄漏和內(nèi)存溢出

內(nèi)存溢出:實(shí)實(shí)在在的內(nèi)存空間不足導(dǎo)致;
內(nèi)存泄漏:該釋放的對(duì)象沒有釋放埠通,多見于自己使用容器保存元素的情況下赎离。

7.JDK工具以及MAT工具

下面工具可以采用該代碼進(jìn)行測(cè)試JinfoTest.java

7.1 jps

列出當(dāng)前機(jī)器上正在運(yùn)行的虛擬機(jī)進(jìn)程

-q :僅僅顯示VM 標(biāo)示,不顯示jar,class, main參數(shù)等信息.
-m:輸出主函數(shù)傳入的參數(shù). 下的hello 就是在執(zhí)行程序時(shí)從命令行輸入的參數(shù)
-l: 輸出應(yīng)用程序主類完整package名稱或jar完整名稱.
-v: 列出jvm參數(shù), -Xms20m -Xmx50m是啟動(dòng)程序指定的jvm參數(shù)

7.2 jstat

是用于監(jiān)視虛擬機(jī)各種運(yùn)行狀態(tài)信息的命令行工具端辱。它可以顯示本地或者遠(yuǎn)程虛擬機(jī)進(jìn)程中的類裝載梁剔、內(nèi)存、垃圾收集舞蔽、JIT編譯等運(yùn)行數(shù)據(jù)荣病,在沒有GUI圖形界面,只提供了純文本控制臺(tái)環(huán)境的服務(wù)器上渗柿,它將是運(yùn)行期定位虛擬機(jī)性能問題的首選工具个盆。

假設(shè)需要每250毫秒查詢一次進(jìn)程2764垃圾收集狀況,一共查詢20次朵栖,那命令應(yīng)當(dāng)是:jstat-gc 2764 250 20

常用參數(shù):
-class (類加載器)
-compiler (JIT)
-gc (GC堆狀態(tài))
-gccapacity (各區(qū)大小)
-gccause (最近一次GC統(tǒng)計(jì)和原因)
-gcnew (新區(qū)統(tǒng)計(jì))
-gcnewcapacity (新區(qū)大小)
-gcold (老區(qū)統(tǒng)計(jì))
-gcoldcapacity (老區(qū)大小)
-gcpermcapacity (永久區(qū)大小)
-gcutil (GC統(tǒng)計(jì)匯總)
-printcompilation (HotSpot編譯統(tǒng)計(jì))

7.3 jinfo

查看和修改虛擬機(jī)的參數(shù)

jinfo –sysprops 可以查看由System.getProperties()取得的參數(shù)
jinfo –flag 未被顯式指定的參數(shù)的系統(tǒng)默認(rèn)值
jinfo –flags(注意s)顯示虛擬機(jī)的參數(shù)


jinfo –flag +[參數(shù)] 可以增加參數(shù)颊亮,但是僅限于由java -XX:+PrintFlagsFinal –version查詢出來且為manageable的參數(shù)

jinfo –flag -[參數(shù)] 可以去除參數(shù)

Thread.getAllStackTraces();

7.4 jmap

用于生成堆轉(zhuǎn)儲(chǔ)快照(一般稱為heapdump或dump文件)。jmap的作用并不僅僅是為了獲取dump文件混槐,它還可以查詢finalize執(zhí)行隊(duì)列编兄、Java堆和永久代的詳細(xì)信息,如空間使用率声登、當(dāng)前用的是哪種收集器等狠鸳。和jinfo命令一樣,jmap有不少功能在Windows平臺(tái)下都是受限的悯嗓,除了生成dump文件的-dump選項(xiàng)和用于查看每個(gè)類的實(shí)例件舵、空間占用統(tǒng)計(jì)的-histo選項(xiàng)在所有操作系統(tǒng)都提供之外,其余選項(xiàng)都只能在Linux/Solaris下使用脯厨。

jmap -dump:live,format=b,file=heap.bin <pid>

Sun JDK提供jhat(JVM Heap Analysis Tool)命令與jmap搭配使用铅祸,來分析jmap生成的堆轉(zhuǎn)儲(chǔ)快照。

7.5 jhat (1.9之后刪除了)

jhat dump文件名

后屏幕顯示“Server is ready.”的提示后合武,用戶在瀏覽器中鍵入http://localhost:7000/就可以訪問詳情

7.6 jstack

(Stack Trace for Java)命令用于生成虛擬機(jī)當(dāng)前時(shí)刻的線程快照临梗。線程快照就是當(dāng)前虛擬機(jī)內(nèi)每一條線程正在執(zhí)行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現(xiàn)長(zhǎng)時(shí)間停頓的原因稼跳,如線程間死鎖盟庞、死循環(huán)、請(qǐng)求外部資源導(dǎo)致的長(zhǎng)時(shí)間等待等都是導(dǎo)致線程長(zhǎng)時(shí)間停頓的常見原因汤善。

在代碼中可以用java.lang.Thread類的getAllStackTraces()方法用于獲取虛擬機(jī)中所有線程的StackTraceElement對(duì)象什猖。使用這個(gè)方法可以通過簡(jiǎn)單的幾行代碼就完成jstack的大部分功能票彪,在實(shí)際項(xiàng)目中不妨調(diào)用這個(gè)方法做個(gè)管理員頁(yè)面,可以隨時(shí)使用瀏覽器來查看線程堆棧不狮。

7.7 JConsole

管理遠(yuǎn)程進(jìn)程需要在遠(yuǎn)程程序的啟動(dòng)參數(shù)中增加:

-Djava.rmi.server.hostname=…..
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8888
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

7.8 visualvm

插件中心地址https://visualvm.github.io

但是注意版本問題降铸,不同的JDK所帶的visualvm是不一樣的,下載插件時(shí)需要下對(duì)應(yīng)的版本摇零。

7.9 MAT

可以采用OOM.java代碼進(jìn)行測(cè)試
-XX:+HeapDumpOnOutOfMemoryError 內(nèi)存OOM時(shí)生成hprof文件
使用mat工具分析內(nèi)存占用:可以幫助分析內(nèi)存泄漏推掸。

淺堆和深堆

  • 淺堆 :(Shallow Heap)是指一個(gè)對(duì)象所消耗的內(nèi)存。例如驻仅,在32位系統(tǒng)中终佛,一個(gè)對(duì)象引用會(huì)占據(jù)4個(gè)字節(jié),一個(gè)int類型會(huì)占據(jù)4個(gè)字節(jié)雾家,long型變量會(huì)占據(jù)8個(gè)字節(jié)铃彰,每個(gè)對(duì)象頭需要占用8個(gè)字節(jié)。
  • 深堆 :這個(gè)對(duì)象被GC回收后芯咧,可以真實(shí)釋放的內(nèi)存大小牙捉,也就是只能通過對(duì)象被直接間接訪問到的所有對(duì)象的集合。通俗地說敬飒,就是指僅被對(duì)象所持有的對(duì)象的集合邪铲。深堆是指對(duì)象的保留集中所有的對(duì)象的淺堆大小之和。
  • 舉例:對(duì)象A引用了C和D无拗,對(duì)象B引用了C和E带到。那么對(duì)象A的淺堆大小只是A本身,不含C和D英染,而A的實(shí)際大小為A揽惹、C、D三者之和四康。而A的深堆大小為A與D之和搪搏,由于對(duì)象C還可以通過對(duì)象B訪問到,因此不在對(duì)象A的深堆范圍內(nèi)闪金。

參考

  • 1)享學(xué)課堂Mark老師筆記
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末疯溺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子哎垦,更是在濱河造成了極大的恐慌囱嫩,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漏设,死亡現(xiàn)場(chǎng)離奇詭異墨闲,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)愿题,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門损俭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人潘酗,你說我怎么就攤上這事杆兵。” “怎么了仔夺?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵琐脏,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我缸兔,道長(zhǎng)日裙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任惰蜜,我火速辦了婚禮昂拂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抛猖。我一直安慰自己格侯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布财著。 她就那樣靜靜地躺著联四,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撑教。 梳的紋絲不亂的頭發(fā)上朝墩,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音伟姐,去河邊找鬼收苏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛愤兵,可吹牛的內(nèi)容都是我干的倒戏。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼恐似,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼杜跷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起矫夷,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤葛闷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后双藕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淑趾,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年忧陪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扣泊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片近范。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖延蟹,靈堂內(nèi)的尸體忽然破棺而出评矩,到底是詐尸還是另有隱情,我是刑警寧澤阱飘,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布斥杜,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜边坤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一祠够、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)爪飘。三九已至义起,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間师崎,已是汗流浹背默终。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留犁罩,地道東北人齐蔽。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像床估,于是被迫代替她去往敵國(guó)和親含滴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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