垃圾回收算法梳理

4.jvm調優

1. 常見問題

1.1 內存泄漏

內存泄漏一般可以理解爲系統資源(各方面的資源,堆、棧、線程等)在錯誤使用的情況下,導致使用完畢的資源無法回收(或沒有回收),從而導致新的資源分配請求無法完成,引起系統錯誤。整個JVM內存大小=年輕代大小 + 年老代大小 + 持久代大小,目前來說,常遇到的泄漏問題如下:

年老代堆空間被佔滿

 年老代堆空間被佔滿
 異常: java.lang.OutOfMemoryError: Java heap space

這是最典型的內存泄漏方式,簡單說就是所有堆空間都被無法回收的垃圾對象佔滿,虛擬機無法再在分配新空間。這種情況一般來說是因爲內存泄漏或者內存不足造成的。某些情況因爲長期的無法釋放對象,運行時間長了以後導致對象數量增多,從而導致的內存泄漏。另外一種就是因爲系統的原因,大併發加上大對象,Survivor Space區域內存不夠,大量的對象進入到了老年代,然而老年代的內存也不足時,從而產生了Full GC,但是這個時候Full GC也無發回收。這個時候就會產生java.lang.OutOfMemoryError: Java heap space

解決方案如下:

  1. 代碼內的內存泄漏可以通過一些分析工具進行分析,然後找出泄漏點進行改善。
  2. 第二種原因導致的OutOfMemoryError可以通過,優化代碼和增加Survivor Space等方式去優化。

持久代被佔滿

持久代被佔滿
異常:java.lang.OutOfMemoryError: PermGen space

Perm空間被佔滿。無法爲新的class分配存儲空間而引發的異常。這個異常以前是沒有的,但是在Java反射大量使用的今天這個異常比較常見了。主要原因就是大量動態反射生成的類不斷被加載,最終導致Perm區被佔滿。 解決方案:

  1. 增加持久代的空間 -XX:MaxPermSize=100M。
  2. 如果有自定義類加載的需要排查下自己的代碼問題。

堆棧溢出

堆棧溢出
異常:java.lang.StackOverflowError

一般就是遞歸沒返回,或者循環調用造成

線程堆棧滿

線程堆棧滿
異常:Fatal: Stack size too small

java中一個線程的空間大小是有限制的。JDK5.0以後這個值是1M。與這個線程相關的數據將會保存在其中。但是當線程空間滿了以後,將會出現上面異常。 解決:增加線程棧大小。-Xss2m。但這個配置無法解決根本問題,還要看代碼部分是否有造成泄漏的部分。

系統內存被佔滿

系統內存被佔滿
異常:java.lang.OutOfMemoryError: unable to create new native thread

這個異常是由於操作系統沒有足夠的資源來產生這個線程造成的。系統創建線程時,除了要在Java堆中分配內存外,操作系統本身也需要分配資源來創建線程。因此,當線程數量大到一定程度以後,堆中或許還有空間,但是操作系統分配不出資源來了,就出現這個異常了。 分配給Java虛擬機的內存愈多,系統剩餘的資源就越少,因此,當系統內存固定時,分配給Java虛擬機的內存越多,那麼,系統總共能夠產生的線程也就越少,兩者成反比的關係。同時,可以通過修改-Xss來減少分配給單個線程的空間,也可以增加系統總共內生產的線程數。 解決:

1. 重新設計系統減少線程數量。
2. 線程數量不能減少的情況下,通過-Xss減小單個線程大小。以便能生產更多的線程。

2 優化方法

2.1 優化目標

優化jvm其實是爲了滿足高吞吐,低延遲需求來優化GC,之前遇到的情況中,其實不優化GC也是可以正常於行的,只不過偶爾會因爲高併發給壓垮,但是也可以通過其他方式來解決這個問題。

2.2 優化GC步驟

  1. 首先需要觀察目前垃圾回收的情況,分析出老年代和年輕代回收的情況,適當的去調整內存大小和-XX:SurvivorRatio的比例。
  2. 根據垃圾收集器的特性,選擇適合自己業務的垃圾收集器,一般來說現在的WEB服務都是CMS+ParNew收集器。根據CMS收集器一般來說就會產生大量碎片,根據自己的需求懸着相應的壓縮頻率即可。
  3. 不斷的調整jvm內存比例,老年代、年輕代、以及持久代的比例,直到測試出一個比較滿意的值。

2.3 優化總結

總的來說GC優化不僅僅是加大內存可以解決的。需要綜合下業務特徵和GC的時間,減少新生代大小可以縮短新生代GC停頓時間,因爲這樣被複制到survivor區域或者被提升的數據更少,但是這樣一來yangGC的頻率就會很高,而且會有更多的垃圾進入到了老年代。如果增加新生代大小又會導致回收的時間和複製的時間變高,所以一般來說需要在這個中間進行折中。

像是針對大部分數據都在Eden區域被回收,並且幾乎沒有對象在survivor區域死亡的場景,完全可以減少-XX:MaxTenuringThreshold這個參數,讓數據提早進入Old,減少survivor區域的複製,來提高效率。

3.案例分析

3.1 案例1 Intellij IDEA 2016優化

公司系統參數

-server 
-Xms2g 
-Xmx2g
-Xmn768m 
-XX:+UseConcMarkSweepGC 
-XX:+UseParNewGC 
-XX:CMSInitiatingOccupancyFraction=60 
-XX:CMSTriggerRatio=70 
-Xloggc:/data/bpm.coffee.session/logs/gc_20160704_110306.log 
-XX:+PrintGCDateStamps 
-XX:+PrintGCDetails 
-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=/data/bpm.coffee.session/tmp/heapdump_20160704_110306.hprof

網上給的配置參數

-server
-Xms6000M
-Xmx6000M
-Xmn500M
-XX:PermSize=500M
-XX:MaxPermSize=500M
-XX:SurvivorRatio=65536
-XX:MaxTenuringThreshold=0
-Xnoclassgc
-XX:+DisableExplicitGC
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
-XX:+CMSClassUnloadingEnabled
-XX:-CMSParallelRemarkEnabled
-XX:CMSInitiatingOccupancyFraction=90
-XX:SoftRefLRUPolicyMSPerMB=0
-XX:+PrintClassHistogram
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC
-Xloggc:log/gc.log

Intellij IDEA 2016 默認參數

-Xms158m
-Xmx750m
-XX:MaxPermSize=350m
-XX:ReservedCodeCacheSize=240m
-XX:+UseCompressedOops

Intellij IDEA 2016

情景分析:

  1. -Xms2g -Xmx2g 這兩個主要是設置初始堆大小和最大堆的大小,對比目前公司系統網上參數Intellij IDEA默認參數,總的來說在於場景問題,-Xms初始堆針對於服務器端的來說一般會設置跟最大堆一樣大,爲什麼呢?因爲對於服務器來說啓動時間並不是最重要的,如果不夠了再去申請,這個對於大併發的場景來說是不合適的。而針對於客戶端Intellij IDEA來說,重點是啓動時間,所以初始堆設置比較小,這樣啓動的時間比較快。
  2. -server 這個參數主要是用來指定運行模式的,指定了-server那麼啓動的速度就會慢一些,所以針對於IDEA這種編輯器來說一般都使用默認的client模式
  3. -XX:+UseCompressedOops 使用compressed pointers。這個參數默認在64bit的環境下默認啓動,但是如果JVM的內存達到32G後,這個參數就會默認爲不啓動,因爲32G內存後,壓縮就沒有多大必要了,要管理那麼大的內存指針也需要很大的寬度了
  4. -XX:ReservedCodeCacheSize=240m 這個主要是用來編譯代碼的,針對server來說用處不是很大.
  5. 針對於服務端來說-XX:+UseConcMarkSweepGC 老年代基本都是使用cms收集器,而年輕代都是使用-XX:+UseParNewGC進行收集的。
  6. -XX:CMSInitiatingOccupancyFraction=60 這個參數主要是告訴cms收集器,內存到60%的時候進行回收,這個比例主要還是針對系統業務來決定的,太低容易內存溢出,太高容易內存浪費,而且老年代垃圾回收頻率高。一般來說都是在70到90之間,60過低了。
  7. -Xloggc 這個參數一般都會打開,能夠很好的排查jvm的內存溢出.
  8. -Xnoclassgc 這個針對一般業務來說都會打開,不讓class進行gc.
  9. -XX:+DisableExplicitGC web業務來說,由於大家開發水平不一致,難保有人會使用System.gc(),高併發的場景這個肯定會是一個很大的影響,所以一般都關掉
  10. -XX+UseCMSCompactAtFullCollection消除cms碎片
  11. -XX:CMSFullGCsBeforeCompaction 這個上面填的0,如果沒有特殊場景,最好設置成1或者2.
  12. -XX:-CMSParallelRemarkEnabled 主要用來降低cms標記的停頓,但是這個參數會增加碎片化,
  13. SoftRefLRUPolicyMSPerMB 這個參數指定了軟飲用停留的時間,一般來說設置爲0就好了,感覺生產中這個參數影響並不大,如果是針對單機內存使用緩存比較多的場景,這個值可以設置到1s左右,增加加吞吐量。

總結

jvm的調優需要根據自己的業務場景進行調整,沒有萬能的參數,但是總的來說,在生產環境中都會打開這幾個必須的參數

verbose: gc
Xloggc:<pathtofile>
XX:+PrintGCDetails
XX:+PrintTenuringDistribution
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章