天天看點

Java虛拟機詳解05----垃圾收集器及GC參數

本文主要内容:

堆的回顧

串行收集器

并行收集器

cms收集器

零、堆的回顧:

新生代中的98%對象都是“朝生夕死”的,是以并不需要按照1:1的比例來劃分記憶體空間,而是将記憶體分為一塊比較大的eden空間和兩塊較小的survivor空間,每次使用eden和其中一塊survivor。當回收時,将eden和survivor中還存活着的對象一次性地複制到另外一塊survivor空間上,最後清理掉eden和剛才用過的survivor空間。hotspot虛拟機預設eden和survivor的大小比例是8:1,也就是說,每次新生代中可用記憶體空間為整個新生代容量的90%(80%+10%),隻有10%的空間會被浪費。

當然,98%的對象可回收隻是一般場景下的資料,我們沒有辦法保證每次回收都隻有不多于10%的對象存活,當survivor空間不夠用時,需要依賴于老年代進行配置設定擔保,是以大對象直接進入老年代。

堆的結構如下圖所示:

Java虛拟機詳解05----垃圾收集器及GC參數

垃圾收集器:

如果說收集算法時記憶體回收的方法論,那麼垃圾收集器就是記憶體回收的具體實作。

雖然我們在對各種收集器進行比較,但并非為了挑出一個最好的收集器。因為直到現在位置還沒有最好的收集器出現,更加沒有萬能的收集器,是以我們選擇的隻是對具體應用最合适的收集器。

一、串行收集器:serial收集器

最古老,最穩定

簡單而高效

可能會産生較長的停頓

-xx:+useserialgc

    新生代、老年代都會使用串行回收

      新生代複制算法

    老年代标記-整理

總結:serial收集器對于運作在client模式下的虛拟機來說是一個很好的選擇。

這個收集器是一個單線程的收集器,但它的單線程的意義并不僅僅說明它隻會使用一個cpu或一條收集線程去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其他所有的工作線程,直到它收集結束。收集器的運作過程如下圖所示:

Java虛拟機詳解05----垃圾收集器及GC參數

二、并行收集器:

1、parnew收集器:

parnew收集器其實就是serial收集器新生代的并行版本。

多線程,需要多核支援。

-xx:+useparnewgc

    新生代并行

    老年代串行

-xx:parallelgcthreads 限制線程數量

Java虛拟機詳解05----垃圾收集器及GC參數

2、parallel scanvenge收集器:

類似parnew,但更加關注吞吐量

-xx:+useparallelgc  使用parallel scanvenge收集器:新生代并行,老年代串行

3、parallel old收集器:

parallel old收集器是parallel scanvenge收集器的老年代版本

-xx:+useparallelgc  使用parallel old收集器:新生代并行,老年代并行

如下圖所示:

Java虛拟機詳解05----垃圾收集器及GC參數

各種參數設定:

-xx:maxgcpausemills

    最大停頓時間,機關毫秒

    gc盡力保證回收時間不超過設定值

-xx:gctimeratio

    0-100的取值範圍

    垃圾收集時間占總時間的比

    預設99,即最大允許1%時間做gc

注:這兩個參數是沖突的。因為停頓時間和吞吐量不可能同時調優。我們一方買希望停頓時間少,另外一方面希望吞吐量高,其實這是沖突的。因為:在gc的時候,垃圾回收的工作總量是不變的,如果将停頓時間減少,那頻率就會提高;既然頻率提高了,說明就會頻繁的進行gc,那吞吐量就會減少,性能就會降低。

吞吐量:cpu用于使用者代碼的時間/cpu總消耗時間的比值,即=運作使用者代碼的時間/(運作使用者代碼時間+垃圾收集時間)。比如,虛拟機總共運作了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。

注2:以上所有的收集器當中,當執行gc時,都會stop the world,但是下面的cms收集器卻不會這樣。

三、cms收集器:

cms收集器(concurrent mark sweep:并發标記清除)是一種以擷取最短回收停頓時間為目标的收集器。适合應用在網際網路站或者b/s系統的伺服器上,這類應用尤其重視伺服器的響應速度,希望系統停頓時間最短。

concurrent mark sweep 并發标記清除,并發低停頓

标記-清除算法

并發階段會降低吞吐量(因為停頓時間減少了,于是gc的頻率會變高)

老年代收集器(新生代使用parnew)

-xx:+useconcmarksweepgc   打開這收集器

注:這裡的并發指的是與使用者線程一起執行。

2、cms收集器運作過程:(着重實作了标記的過程)

(1)初始标記

根可以直接關聯到的對象

速度快

(2)并發标記(和使用者線程一起)

主要标記過程,标記全部對象

(3)重新标記

由于并發标記時,使用者線程依然運作,是以在正式清理前,再做修正

(4)并發清除(和使用者線程一起)

基于标記結果,直接清理對象

整個過程如下圖所示:

Java虛拟機詳解05----垃圾收集器及GC參數

其中,初始标記和重新标記時,需要stop the world。

整個過程中耗時最長的是并發标記和并發清除,這兩個過程都可以和使用者線程一起工作。

列印gc日志舉例如下:

Java虛拟機詳解05----垃圾收集器及GC參數

3、cms收集器特點:

(1)盡可能降低停頓

(2)會影響系統整體吞吐量和性能

比如,在使用者線程運作過程中,分一半cpu去做gc,系統性能在gc階段,反應速度就下降一半

(3)清理不徹底

因為在清理階段,使用者線程還在運作,會産生新的垃圾,無法清理

(4)因為和使用者線程一起運作,不能在空間快滿時再清理

-xx:cmsinitiatingoccupancyfraction設定觸發gc的門檻值

如果不幸記憶體預留白間不夠,就會引起concurrent mode failure

我們來看一下concurrent mode failure的日志:

Java虛拟機詳解05----垃圾收集器及GC參數

碰到上圖中的情況,我們需要使用串行收集器作為後備。

4、既然标記清除算法會造成記憶體空間的碎片化,cms收集器為什麼使用标記清除算法而不是使用标記整理算法:

答案:

    cms收集器更加關注停頓,它在做gc的時候是和使用者線程一起工作的(并發執行),如果使用标記整理算法的話,那麼在清理的時候就會去移動可用對象的記憶體空間,那麼應用程式的線程就很有可能找不到應用對象在哪裡。

為了解決碎片的問題,cms收集器會有一些整理上的參數,接下來就來講這個。

5、整理時的各種參數:

-xx:+ usecmscompactatfullcollection     

full gc後,進行一次整理。整理過程是獨占的,會引起停頓時間變長

-xx:+cmsfullgcsbeforecompaction

設定進行幾次full gc後,進行一次碎片整理

-xx:parallelcmsthreads

設定cms的線程數量

四、gc參數的整理:

-xx:+useserialgc:在新生代和老年代使用串行收集器

-xx:survivorratio:設定eden區大小和survivior區大小的比例

-xx:newratio:新生代和老年代的比

-xx:+useparnewgc:在新生代使用并行收集器

-xx:+useparallelgc :新生代使用并行回收收集器

-xx:+useparalleloldgc:老年代使用并行回收收集器

-xx:parallelgcthreads:設定用于垃圾回收的線程數

-xx:+useconcmarksweepgc:新生代使用并行收集器,老年代使用cms+串行收集器

-xx:parallelcmsthreads:設定cms的線程數量

-xx:cmsinitiatingoccupancyfraction:設定cms收集器在老年代空間被使用多少後觸發

-xx:+usecmscompactatfullcollection:設定cms收集器在完成垃圾收集後是否要進行一次記憶體碎片的整理

-xx:cmsfullgcsbeforecompaction:設定進行多少次cms垃圾回收後,進行一次記憶體壓縮

-xx:+cmsclassunloadingenabled:允許對類中繼資料進行回收

-xx:cmsinitiatingpermoccupancyfraction:當永久區占用率達到這一百分比時,啟動cms回收

-xx:usecmsinitiatingoccupancyonly:表示隻在到達閥值的時候,才進行cms回收

最後的總結:

為了減輕gc壓力,我們需要注意些什麼?

軟體如何設計架構(性能的根本在應用)

gc參數屬于微調(設定不合理會影響性能,産生大的延時)

堆空間如何管理和配置設定

代碼如何寫