JVM調優之參數優化

一、先看一段JVM參數配置:

  • -XX:+UseCompressedOops
    -Xms3096m
    -Xmx3096m
    -XX:PermSize=512
    -XX:MaxPermSize=1024m
    -XX:NewSize=2048m
    -XX:MaxNewSize=2048m
    -XX:SurvivorRatio=8

    -verbose:gc
    -Xloggc:/data/dataLogs/gc/gc.log
    -XX:+PrintGCDateStamps
    -XX:+PrintGCDetails

    -XX:+UseConcMarkSweepGC
    -XX:CMSInitiatingOccupancyFraction=70
    -XX:+UseCMSCompactAtFullCollection
    -XX:+CMSClassUnloadingEnabled
    -XX:+DisableExplicitGC

    -XX:+HeapDumpOnOutOfMemoryError
    -XX:HeapDumpPath=/data/dataLogs/dump

參數介紹:
-XX:+UseCompressedOops 這個可以壓縮指針,起到節約內存佔用的新參數。
通常64位JVM消耗的內存會比32位的大1.5倍,這是因爲對象指針在64位架構下,長度會翻倍(更寬的尋址)。
對於那些將要從32位平臺移植到64位的應用來說,平白無辜多了1/2的內存佔用,從JDK1.6開始支持。
-Xms 設置JVM初始堆內存。
-Xmx 設置JVM最大堆內存。此值可以設置與-Xmx相同,以避免每次垃圾回收完成後JVM重新分配內存。
-Xmn:設置年輕代大小。
-Xss:設置每個線程的堆棧大小。JDK5.0以後每個線程堆棧大小爲1M,以前每個線程堆棧大小爲128k.更具應用的線程所需內存大小進行 調整.在相同物理內存下,減小這個值能生成更多的線程.但是操作系統對一個進程內的線程數還是有限制的,不能無限生成,經驗值在3000~5000左右。
-XX:MaxTenuringThreshold:設置垃圾最大年齡.如果設置爲0的話,則年輕代對象不經過Survivor區,直接進入年老代. 對於年老代比較多的應用,可以提高效率.如果將此值設置爲一個較大值,則年輕代對象會在Survivor區進行多次複製,這樣可以增加對象再年輕代的存活 時間,增加在年輕代即被回收的概率。

-XX:MaxPermSize 設置持久代初始值。
-XX:MaxPermSize 設置持久代最大值。
-XX:NewSize 設置年輕代初始值。
-XX:MaxNewSize 設置年輕代最大值。
XX:NewRatio:設置年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代).設置爲2,則年輕代與年老代所佔比值爲1:2,年輕代佔整個堆棧的1/3。
-XX:SurvivorRatio:設置年輕代中Eden區與Survivor區的大小比值.設置爲8,則兩個Survivor區與一個Eden區的比值爲2:8,一個Survivor區佔整個年輕代的1/10。

-verbose:gc 表示輸出虛擬機中GC的詳細情況。
-Xloggc 設置gc日誌路徑。
-XX:+PrintGCDateStamps 輸出GC的時間戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)。
-XX:+PrintGCDetails 輸出GC的詳細日誌。

-XX:+UseConcMarkSweepGC CMS收集,設置年老代爲併發收集。
-XX:+UseCMSCompactAtFullCollection 打開內存空間的壓縮和整理,在Full GC後執行。可能會影響性能,但可以消除內存碎片。
-XX:CMSInitiatingOccupancyFraction 是指設定CMS在對內存佔用率達到70%的時候開始GC(因爲CMS會有浮動垃圾,所以一般都較早啓動GC)。
-XX:+CMSClassUnloadingEnabled 默認情況下,CMS不會處理永久代中的垃圾,可以通過開啓CMSPermGenSweepingEnabled配置來開啓永久代中的垃圾回收,開啓後會有一組後臺線程針對永久代做收集,需要注意的是,觸發永久代進行垃圾收集的指標跟觸發老年代進行垃圾收集的指標是獨立的,老年代的閾值可以通過CMSInitiatingPermOccupancyFraction參數設置,這個參數的默認值是80%。開啓對永久代的垃圾收集只是其中的一步,還需要開啓另一個參數——CMSClassUnloadingEnabled,使得在垃圾收集的時候可以卸載不用的類。
-XX:+DisableExplicitGC,這個參數作用是禁止代碼中顯示調用GC。代碼如何顯示調用GC呢,通過System.gc()函數調用。如果加上了這個JVM啓動參數,那麼代碼中調用System.gc()沒有任何效果。
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath 當發生OutOfMemoryError錯誤時,才能觸發-XX:HeapDumpOnOutOfMemoryError 輸出到-XX:HeapDumpPath指定位置。

二、垃圾回收算法:
1、標記-清除算法(Mark-Sweep)
標記-清除 算法是最基礎的算法,分爲“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成後統一回收掉所有被標記的對象。它主要由兩個缺點:一個是效率問題,標記和清除過程的效率都不高;另一個是空間問題,標記清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致當程序在以後的運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。具體過程如下圖所示:
在這裏插入圖片描述
2、複製算法(Copying)
針對新生代。爲了解決標記清除算法的效率問題,出現了複製算法,它將可用內存按容量劃分爲大小相等的兩塊,每次使用其中的一塊。當這塊的內存用完了,就將還存活着的對象複製到另一塊上面,然後再把已使用過的內存空間一次清理掉。優點是每次都是對其中的一塊進行內存回收,內存分配時就不用考慮內存碎片等複雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。缺點是將內存縮小爲原來的一半,代價太高了一點。

     現在的商業虛擬機都採用複製收集算法來回收新生代,有研究表明,新生代中的對象98%是朝生夕死的,所以並不需要按照1:1的比例來劃分內存空間,而是將內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中的一塊Survivor。當回收時,將Eden和Survivor中還存活着的對象一次性地拷貝到另外一塊Survivor空間上,最後清理掉Eden和剛纔用過的Survivor空間。HotSpot虛擬機默認Eden和Survivor的大小比例是8:1,也就是每次新生代中可用內存空間爲整個新生代容量的90%(80%+10%),只有10%的內存是會被“浪費”的。當然,並不能保證每次回收都只有10%的對象存活,當Survivor空間不夠用時,需要依賴其他內存(這裏指老年代)進行分配擔保(Handle Promotion)。即如果另外一塊Survivor空間沒有足夠的空間存放上一次新生代收集下來的存活對象,這些對象將直接通過分配擔保機制進入老年代。

具體過程如下圖所示:
在這裏插入圖片描述
3、標記-整理算法(Mark-Compact)
針對老年代。爲了解決Copying算法的缺陷,充分利用內存空間,提出了Mark-Compact算法。該算法標記階段和Mark-Sweep一樣,但是在完成標記之後,它不是直接清理可回收對象,而是將存活對象都向一端移動,然後清理掉端邊界以外的內存。具體過程如下圖所示:
在這裏插入圖片描述
4、分代收集算法(Generational Collection)
代收集算法是目前大部分JVM的垃圾收集器採用的算法。它的核心思想是根據對象存活的生命週期將內存劃分爲若干個不同的區域。一般情況下將堆區劃分爲老年代(Tenured Generation)和新生代(Young Generation),老年代的特點是每次垃圾收集時只有少量對象需要被回收,而新生代的特點是每次垃圾回收時都有大量的對象需要被回收,那麼就可以根據不同代的特點採取最適合的收集算法。

	前大部分垃圾收集器對於新生代都採取Copying算法,因爲新生代中每次垃圾回收都要回收大部分對象,也就是說需要複製的操作次數較少,但是實際中並不是按照1:1的比例來劃分新生代的空間的,一般來說是將新生代劃分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當進行回收時,將Eden和Survivor中還存活的對象複製到另一塊Survivor空間中,然後清理掉Eden和剛纔使用過的Survivor空間。 而由於老年代的特點是每次回收都只回收少量對象,一般使用的是Mark-Compact算法。

三、垃圾回收器:
如果說收集算法是內存回收的方法論,垃圾收集器就是內存回收的具體實現。下圖展示了7種不同分代的收集器,如果兩個收集器之間存在連線,就說明他們可以搭配使用。並沒有最好的收集器這一說,我們需要選擇的是對具體應用最合適的收集器。
在這裏插入圖片描述

1、Serial收集器(用於新生代)
在這裏插入圖片描述

	單線程,在進行垃圾收集時必須暫停其他所有的工作線程("Stop the World")。虛擬機運行在Client模式下的默認新生代收集器。簡單而高效(與其他收集器的單線程比),對於限定單個CPU的環境來說,Serial收集器由於沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程效率。

2、ParNew收集器(新生代)
在這裏插入圖片描述

 ParNew收集器其實是Serial收集器的多線程版本,它是許多運行在Server模式下的虛擬機中首選的新生代收集器,因爲除了Serial收集器外,目前只有它能與CMS收集器配合工作。

3、Parallel Scavenge收集器

   “吞吐量優先”收集器,適用於新生代。 使用複製算法,並行多線程,這些特點與ParNew一樣,它的獨特之處是它的關注點與其他收集器不同,CMS等收集器的關注點是儘可能縮短垃圾收集時用戶線程的停頓時間,而Parallel Scavenge收集器的目的則是達到一個可控制的吞吐量(Throughput),即CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,吞吐量=運行用戶代碼時間 /(運行用戶代碼時間+垃圾收集時間),虛擬機總共運行了100分鐘,其中垃圾收集花掉1分鐘,吞吐量就是99%。
   停頓時間越短對於需要與用戶交互的程序來說越好,良好的響應速度能提升用戶的體驗;
   高吞吐量可以最高效率地利用CPU時間,儘快地完成程序的運算任務,主要適合在後臺運算而不太需要太多交互的任務。

參數設置:
-XX:MaxGCPauseMillis 控制最大垃圾收集停頓時間。(大於0的毫秒數)停頓時間縮短是以犧牲吞吐量和新生代空間換取的。(新生代調的小,吞吐量跟着小,垃圾收集時間就短,停頓就小)。
-XX:GCTimeRatio 直接設置吞吐量大小,0<x<100 的整數,允許的最大GC時間=1/(1+x)。
-XX:+UseAdaptiveSizePolicy  一個開關參數,開啓GC自適應調節策略(GC Ergonomics),將內存管理的調優任務(新生代大小-Xmn、Eden與Survivor區的比例-XX:SurvivorRatio、晉升老年代對象年齡-XX: PretenureSizeThreshold 、等細節參數)交給虛擬機完成。這是Parallel Scavenge收集器與ParNew收集器的一個重要區別,另一個是吞吐量。

4、Serial Old收集器

	標記-整理算法。適用於老年代,它是Serial收集器的老年代版本,單線程,使用“標記-整理”算法。主要意義是被Client模式下的虛擬機使用。如果在Server模式下,它還有兩大用途:在JDK1.5及之前的版本中與Parallel Scavenge收集器搭配使用;作爲CMS 收集器的後備預案,在併發收集發生Concurrent Mode Failure的時候使用。運行過程同Serial收集器。

5、Parallel Old收集器
在這裏插入圖片描述

	標記-整理算法。適用於老年代,它是Parallel Scavenge收集器的老年代版本,多線程,使用“標記-整理”算法。在注重吞吐量及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge+Parallel Old收集器。
**6、CMS收集器(Concurrent Mark Sweep)**

在這裏插入圖片描述

“標記-清除”算法。它是一種以獲取最短回收停頓時間爲目標的收集器。優點:併發收集,低停頓。目前很大一部分Java應用都集中在互聯網站或B/S系統的服務端上,這類應用尤其重視服務的響應速度,希望系統停頓時間最短,以給用戶帶來較好的體驗,CMS收集器就非常符合這類應用的需求。運作過程較複雜,分爲4個步驟:
初始標記(CMS initial mark):需要“Stop The World”,標記GC Roots能直接關聯到的對象,速度快。
併發標記(CMS concurrent mark):進行GC Roots Tracing 過程
重新標記(CMS remark):需要“Stop The World”,修正併發標記期間,因用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄。停頓時間:初始標記<重新標記<<併發標記
併發清除(CMS concurrent sweep):時間較長。

缺點:
對CPU資源非常敏感,面向併發設計的程序都會對CPU資源較敏感。CMS默認的回收線程數: (CPU數量+3)/4
無法處理浮動垃圾,可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC的產生。併發清理階段用戶程序運行產生的垃圾過了標記階段所以無法在本次收集中清理掉,稱爲浮動垃圾。CMS收集器默認在老年代使用了68%的空間後被激活。若老年代增長的不是很快,可以適當調高參數-XX:CMSInitiatingOccupancyFraction  提高觸發百分比,但調得太高會容易導致“Concurrent Mode Failure”失敗。
基於“標記-清除”算法會產生大量空間碎片。提供開關參數-XX:+UseCMSCompactAtFullCollection 用於在“ 享受”完Full GC服務之後進行碎片整理過程,內存整理的過程是無法併發的。但是停頓時間會變長。

7、G1收集器(Garbage First)

它是當前收集器技術發展的最前沿成果。與CMS相比有兩個顯著改進:
基於“標記-整理”算法實現收集器
非常精確地控制停頓

G1收集器可以在幾乎不犧牲吞吐量的前提下完成低停頓的內存回收,這是由於它能夠極力避免全區域的垃圾收集,之前的收集器進行收集的範圍都是整個新生代或老年代,而G1將整個Java堆(包括新生代、老年代)劃分爲多個大小固定的獨立區域(Region),並且跟蹤這些區域裏面的垃圾堆積程度,在後臺維護一個優先列表,每次根據允許的收集時間,優先回收垃圾最多的區域(這就是Garbage First名稱的由來)。區域劃分、有優先級的區域回收,保證了G1收集器在有限的時間內可以獲得最高的收集效率。

三、gc日誌:
在這裏插入圖片描述
在這裏插入圖片描述

作者簡介:就職於甜橙金融信息技術部,負責服務端開發,專注於微服務、分佈式、性能調優、高可用,歡迎各位同仁溝通交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章