JVM GC Collector工做原理及優化

JVM 調優主要是調整GC以及一些執行參數:java

目標:web

堆不要太大,否則單次GC的時間過長致使服務器沒法響應的問題算法

壓力測試的時候TPS平穩安全

儘可能避免full GC服務器

檢查是否用了並行的垃圾回收器多線程

 

參數:併發

-server執行,開啓優化oracle

採用並行gc collector, -XX:+UseParallelGC +XX:+UseParallelOldGC +XX:+UseConcMarkSweepGCjvm

-Xmx不要太大,否則單次gc的過程可能太長,大內存機器能夠採用多個實例的方式性能

-Xms不要過小,否則jvm須要屢次調整堆的大小,增長gc次數,影響了啓動性能。

同理-XX:MaxPermSize

-XX:NewRatio新生代與老年代的比例要合適,這個須要看應用類型,通常新生代的對象很快就會被GC掉了。

線程池的大小要合適,線程上下文切換也是很耗資源的。

不要去調整一些你不徹底瞭解的參數。

還有-Xverify:none能夠加快啓動速度,但字節碼問題查錯很麻煩。

 

字節碼調優應該避免吧,容易觸發一些jvm自己的bug,這些參數缺乏實際場景的測試。

CMS垃圾收集器的相關參數,請讀取相關資料。貌似CMS不會去移動對象的去使得空間更加緊湊。

G1垃圾收集器的工做原理,G1能夠Compact Heap Region以減小內存碎片問題。

 

相關連接:一個Oracle jvm部門裏面的員工的blog: https://blogs.oracle.com/jonthecollector/ 裏面的文章頗有價值。

 

一些命令:

// 監控gc的狀況,每隔2秒輸出一次gc的信息
jstat -gcutil pid 2000

// 每列的語言以下
S0(Survivor0) S1(Survivor1) E(Eden) O(Old) P(Perment) YGC (Young GC Count) YGCT(Young GC Time) FGC(Full GC Count) FGCT(Full GC Time)

 

 Memory Management In The Java HotSpot Virtual Machine

一、爲何要使用自動內存管理

爲了消除手動內存管理帶來的複雜性以及內存泄漏,dangling引用,內存碎片等一系列問題

二、動態內存分配是一個比較複雜的工做,由於它要使用內存的分配及釋放足夠快,同時還要考慮內存碎片問題。

三、垃圾回收器必須儘可能少使得程序暫停,同時也須要在 回收耗時、空間大小,回收次數這幾個方面取得一個平衡,另外還須要控制內存碎片。

四、垃圾回收器必須是可擴展,它不該該成爲程序性能的瓶頸,它應該能夠在多線程/多CPU的環境並行得執行。

五、垃圾回收器最好可以併發地進行內存回收工做,併發狀況下,堆被劃分紅幾個區域,這些區域會被併發地進行回收,由此減小了回收工做所引發的程序的暫停。

六、Compacting vs Non-Compacting vs Copying

七、垃圾回收器性能評判的幾個指標:GC時間佔程序運行時間的比重,非GC時間佔比,GC引發的暫停時間,回收的頻率,檢測到垃圾對象的速度,回收器工做消耗的內存大小。

八、內存分代回收,比較流行分爲young generation(新生代)及old generation(老年代),大多數分配的對象不會存活得過久,一般只有一小部分對象能夠進化到老年代。所以新生代的區域回收得比較頻繁,而老年代空間經過佔用比較多的空間,所以各個區域會使用不一樣的算法進行垃圾回收。好比-XX:NewRatio=4表示 young generation : old generation = 1 : 4。在HotSpot Virutial Machine中,內存被分爲三個區域,young generation, old generation和permenent generation,大部分對象都是在新生代進行分配的,有些對象會直接在老年代進行分配。新生代的空間由Eden生兩個survivor區域來組成,一個survivor用來存放至少存活超過一次young generation GC的對象,而另外一個survivor空間是空的,直接下一次回收的時候會被使用。一個suvivor與Eden的大小比值能夠用-XX:SurvivorRatio=n來表示(1/n=survivor : Eden)

當old generation因塞滿而沒法存放young generation升級上來的對象時,將觸發full GC,這時大部分的收集器會用老年代的算法去GC整個Heap(除了CMS Collector外, CMS Collector的老年代算法沒法回收新生代區域)。

 

快速分配,在大片鏈接的內存塊中進行分配內存的效率是很高的,能夠利用bump-the-pointer技術來進行分配,分配器會記住下一次分配的起點。對於多線程的程序來講,內存分配操做須要是線程安全的,若是使用全局鎖的話這會下降性能並形成性能瓶頸,相應的HotSpot採用一種叫作Thread-Local Allocation Buffers的技術(線程本身的內存分配緩衝區),使得減小獲取全局鎖的操做,一般TLAB佔用大概1%的Edgen的大小。結合TLAB和bump-the-pointer技術,一般分配一個對象空間只須要10條本地指令。

 

Serial Collector

young generation使用Serial Collector進行回收的過程

當使用Serial Collector時候,young generation和old generation的回收工做都是使用單個CPU線性執行的,回收過程當中將stop-the-world。下圖展現了Serial Collector對young generation進行回收時過程,Eden區域存活的對象被複制到那個空的Survivor塊(圖中用to標記),若是對象太大超出了Survivor的大小,那麼它將直接被copy到old generation區域,而非空的survivor(圖中用from標記)中仍然年輕的對象也被複制到空的survivor中(圖中用to標記),而相對比較「老」的對象則被複制到old generation區域中。

注意:若是"To" survivor被塞滿了話,Eden和"From"Survivor區域尚未被copy的對象將直接被複制到old generation中(不管它們存活多少了多少代)。

在完成了對young generation的回收以後,young generation中Eden區域和"From" survivor都被被清空,只有"To" survivor中有存活的對象。這時,"From"和"To" Survivor將對換,以下圖所示:

注意,存在old generation引用young generation對象的狀況,爲了不進行young gc的時候掃描old generation,老年代對new generation的引用被記錄在一個叫作card table的cache中。

old generation使用Serial Collector進行回收的過程:

當使用Serial Collector對old generation和permenent generation進行回收的時候,它將使用一種mark-sweep-compact的回收算法:

Mark階段:Collector檢測那些對象仍然存活。

Sweep階段:Collector掃描整個old generation或者permenent generation,

Compact階段:Collector將存活的對象「滑動」到old generation的首部,全部連續的空間放在old generation的尾部,這樣方便利用bump-the-pointer來實現快速地分配對象。以下圖所示:

何時應該使用Serial Collector。

大部分以Client-Style(java -client)運行的程序都使用這種收集器,這類程序對回收引種的程序暫停時候不敏感。在如今的硬件條件下,Serial Collector可以管理64MB大小的堆空間(如今應該能夠256MB了吧)。

在JavaSE5中,運行非server-class的機器上默認使用Serial Collector,但用戶可使用-XX:+UseSerialGC命令參數來指定使用Serial Collector。

Parallel Collector(也被稱爲Throughput Collector)

現在,不少程序運行在擁有大內存多CPU的機器上面,Parallel Collector就是爲了在垃圾回收過程當中充分利用CPU資源而開發的。

使用Parallel Collector對young generation進行垃圾回收的過程:

Parallel Collector使用一種相似於Serial Collector對young generation回收的算法的並行版本,回收時它仍然會stop-the-world,但在回收的過程當中它並行地使用多個cpu並行地執行,由些來減小垃圾回收所佔用的時候並提高程序運行時間的佔比。

使用Parallel Collector對old generation進行回收

Parallel Collector使用了與Serial Collector一樣的mark-sweep-compact的回收算法。

何時使用Parallel Collector

運行在多cpu以及對中止時間不敏感的程序能夠從使用parallel collector中受益,不頻繁,耗時較長的針對old generation區域回收的仍然會發生, 批量處理,計費,工資以及科學計算這類程序比較適合使用Parallel Collector。

JavaSE5中,運行server-class的機器上默認使用Parallel Collector,用戶可使用-XX:UseParallelGC命令參數來顯示指定使用Parallel Collector

 

Parallel Compacting Collector

Parallel Compacting Collector在JavaSE5.0 update 6被引入,與Parallel Collector不一樣的是,它使用了一種新的算法來對old generation進行回收,最終Parallel Compacting Collector將取代Parallel Collector

回收young generation時,Parallel Compacting Collector使用了與Parallel Collector一樣的算法。

回收old generation和permenent generation時,Parallel Compacting Collector仍然會stop-the-world,但在整理存活對象的時候大部分是並行的。Parallel Compacting Collector使用三個階段進行,

1、young/old/permenent generation區域被劃分紅幾個固定大小的區域

2、marking階段,程序中仍然能夠引用到的存活的對象被劃分給幾個garbage collection threads中,而後mark工做是併發執行地,當一個對象被標記爲存活的時候,它所在的regioin的大小將會被更新。

3、Summary階段,經過前幾回的收集,generation空間的首部會存活的對象會比較密集,經過compacting能回收的空間比較少,所以不值得在上面進行compacting,因此summary階段所作的第一件事就是檢驗regions的密度,從最左邊開始,直到碰到一個密度比較小,值得花時間去compacting的region,而後從這個region開始,compacting右邊的region。summary階段計算並存放被compacting region的新的首地址(這個階段並無真正地去Compacting)。注意:summary段是單線程執行的,儘管它能夠實現爲併發執行。但事實代表併發執行的

4、compacting階段,在這個階段中,利用上一階計算出來的Compacting信息,各個線程能夠獨立地往region移動對象。Compacting完成以後,堆空間的後部將釋放出一片連續的空間。

何時使用Parallel Compacting Collector

在Parallel Collector的基礎上,Parallel Compacting Collector進一步減小了因爲回收old generation所消耗的時候,進一步知足對垃圾回收引發的暫停時間 比較敏感的程序。但須要注意的是,Parallel Compacting Collector 能夠不適合那些運行在大型機/刀片機的程序,這種機器上是不容許單獨一個程序佔用幾個cpu過長時間,在這種環境下能夠考慮利用參數-XX:ParallelGCThread=n,或者選擇另外的垃圾回收器。

須要使用-XX:+UseParallelOldGC來顯式指定使用Parallel Compacting Collector,這個參數的名字有點奇怪,這裏的"Old"是指old generation。

 

Concurrent Mark-Sweep (CMS) Collector

對於不少應用來講,應用運行時間佔比(throughput)沒有響應時間這麼重要,young generation區域的回收一般不會形成太長時間的暫停。可是old generation的回收,儘管不是很頻繁,但一般會強制應用暫停比較長的時間。爲了解決這個問題,HotSpot JVM 引入了一個叫作concurrent mark-sweep (CMS)的收集器,也叫作low-latency collector(低延遲迴收器)

CMS回收young generation的過程:

CMS與Parallel Collector使用一樣的方式回收young generation

CMS回收old generation的過程:

CMS在回收old generation大多數時候都是在程序運行時併發地執行,在開始一次完整的回收以前,CMS須要暫停一下程序(stop-the-world),這個過程叫作初始標記(Initial Mark),這個過程當中查找程序代碼中能夠直接引用到的對象(一般是線程棧上的對象引用),而後在併發標記階段,CMS去標記全部可達的對象,由於這個工做是併發進行的,應用同時也在更新一些字段的引用,因此在併發標記以後須要來一個stop-the-world,將新產生的對象標記完整,這個過程被稱爲remark,remark比initial mark更加耗時,因此通常使用多個線程併發地執行來提交效率。

在remark結束以後,全部的可達的對象都被標記了,而後接下來的併發掃描階段將回收垃圾,下圖闡述了CMS Collector與Serial  mark-sweep-compact Collector之間的差異:

 由於有一些工做,好比在remark階段從新遍歷對象,增長了collector的工做量,因此CMS回收時佔用的CPU和內存資源也更多,但它減小了應用的中止時間。

須要注意的是,CMS collector是惟一一個不會去compact(整理)內存的收集器,以下圖所示,這節省了一些時間,但由於這些空間並不鏈接,bump-the-pointer也不奏效了,所以它須要使用一個free列表去記錄可以使用的空間,而後在分配時去查找這個list。所以分配空間的操做效率相對要低一些,同時,這也會形成回收young generation的一些負擔,所以回收young generation時須要從young generation裏面複製一些存活的對象到old generation中,內存碎片的風險也增長了。

另外,與其它收集器不一樣的是,當old generation滿了以後,它並不會發起一個old generation的回收,相反,它會嘗試在尚未滿的時候發起一次回收(由於在concurrent mark階段程序是併發運行地),以便可以用完以前可以完成回收,不然它將轉爲使用與parallel collector 和serial collector同樣stop-the-world方法去回收這部分空間。爲了不這一點,CMS Collector定時記錄了一些垃圾回收的數據,好比回收的速率,而後在恰當的時候觸發回收操做,避免在用完的時候再進行回收。另外,當old generation佔用超過必定程度以後,CMS Collector也會去發起一次回收操做,能夠用-XX:CMSInitiatingOccupanyFraction=n,n是old generation大小的佔比,默認是68。

總之,CMS可以減小大部分程序因爲回收工做而被暫停的時間 ,但結果的代價是:回收young generation變慢了,程序運行時間佔比降低,以及更大的堆空間消耗。

增量式的回收

CMS Collector能夠運行在增量回收的模式下,這種模式下,young generation回收過程的時候被分爲幾個小塊的時間段。以減小stop-the-world時間。

CMS Collector適合那些對暫時時間比較敏感,容許GC操做併發地使用CPU,並且有大量存活對象的應用,好比web server。

必須使用-XX:+UseConcMarkSweepGC來顯示指定使用CMS Collector,若是須要運行在增量模式下,必須使用-XX:+CMSIncrementalMode參數。

 

 默認堆大小

在-server模式下,jvm的默認初始heap大小是1/64的物理內存(最多1GB),默認最大堆大小是1/4物理內存大小(最大1GB)

-client模式下,默認4MB初始heap大小及64MB的最大堆大小。固然這些均可以經過命令參數進行覆蓋。

 

其它參數

另外還可使用-XX:MaxGCPauseMillis=n來指定GC形成的最大停頓時間,這個時間不必定能完成,不能完成的話,Collector會在堆未佔滿的狀況觸發回收操做。

另外也可使用-XX:GCTimeRatio=n來設置GC時間佔比(GC:程序運行時間),好比GCTimeRatio=4的狀況下,GC時間將最大佔用20%的時間。和-XX:MaxGCPauseMillis同樣,若是不能知足要求,Collector將在geneartion佔滿以前觸發回收操做。

相關文章
相關標籤/搜索