大型跨境电商 JVM 调优经历
大型跨境電商 JVM 調優經歷
前提:
某大型跨境電商業務發展非常快,線上機器擴容也很頻繁,但是對于線上機器的運行情況,特別是jvm內存的情況,一直沒有一個統一的標準來給到各個應用服務的owner。經過618大促之后,和運維的同學討論了下,希望將線上服務器的jvm參數標準化,可以以一個統一的方式給到各個應用,提升線上服務器的穩定性,同時減少大家都去調整jvm參數的時間。
參考了之前在淘寶天貓工作的公司的經歷:經過大家討論,根據jdk的版本以及線上機器配置,確定了一個推薦的默認jvm模版:
最終推薦的jvm模版:
jdk版本 機器配置 建議jvm參數 備注
jdk1.7 6V8G -server -Xms4g -Xmx4g -Xmn2g -Xss768k -XX:PermSize=512m -XX:MaxPermSize=512m
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSClassUnloadingEnabled
-XX:+DisableExplicitGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=68
-verbose:gc -XX:+PrintGCDetails -Xloggc:{CATALINA_BASE}/logs/gc.log -XX:+PrintGCDateStamps
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath={CATALINA_BASE}/logs 前臺
jdk1.7 8V8G -server -Xms4g -Xmx4g -Xmn2g -Xss768k -XX:PermSize=512m -XX:MaxPermSize=512m
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSClassUnloadingEnabled
-XX:+DisableExplicitGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=68
-verbose:gc -XX:+PrintGCDetails -Xloggc:{CATALINA_BASE}/logs/gc.log -XX:+PrintGCDateStamps
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath={CATALINA_BASE}/logs 前臺
jdk1.7 4V8G -server -Xms4g -Xmx4g -Xmn2g -Xss768k -XX:PermSize=512m -XX:MaxPermSize=512m
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSClassUnloadingEnabled
-XX:+DisableExplicitGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=68
-verbose:gc -XX:+PrintGCDetails -Xloggc:{CATALINA_BASE}/logs/gc.log -XX:+PrintGCDateStamps
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath={CATALINA_BASE}/logs 前臺
jdk1.7 6V8G -server -Xms4g -Xmx4g -XX:MaxPermSize=512m
-verbose:gc -XX:+PrintGCDetails -Xloggc{CATALINA_BASE}/logs/gc.log -XX:+PrintGCTimeStamps \ 后臺
某互聯網(bat)公司的推薦配置:
配置說明:
堆設置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:設置年輕代大小
-XX:NewRatio=n:設置年輕代和年老代的比值。如:為3,表示年輕代與年老代比值為1:3,年輕代占整個年輕代年老代和的1/4
-XX:SurvivorRatio=n:年輕代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個。如:3,表示Eden:Survivor=3:2,一個Survivor區占整個年輕代的1/5
-XX:MaxPermSize=n:設置持久代大小
收集器設置
-XX:+UseSerialGC:設置串行收集器
-XX:+UseParallelGC:設置并行收集器
-XX:+UseParalledlOldGC:設置并行年老代收集器
-XX:+UseConcMarkSweepGC:設置并發收集器
垃圾回收統計信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器設置
-XX:ParallelGCThreads=n:設置并行收集器收集時使用的CPU數。并行收集線程數。
-XX:MaxGCPauseMillis=n:設置并行收集最大暫停時間
-XX:GCTimeRatio=n:設置垃圾回收時間占程序運行時間的百分比。公式為1/(1+n)
并發收集器設置
-XX:+CMSIncrementalMode:設置為增量模式。適用于單CPU情況。
-XX:ParallelGCThreads=n:設置并發收集器年輕代收集方式為并行收集時,使用的CPU數。并行收集線程數。
參數解釋:
-Xms3072m -Xmx3072m
針對JVM堆的設置,通過-Xms -Xmx限定其最小、最大值
-Xmn1024m設置年輕代大小為1024m
整個JVM內存大小=年輕代大小 + 年老代大小 + 持久代大小(perm)。
-Xss768k 設置每個線程的堆棧大小。JDK5.0以后每個線程堆棧大小為1M,以前每個線程堆棧大小為256K。更具應用的線程所需內存大小進行調整。在相同物理內存下,減小這個值能生成更多的線程。但是操作系統對一個進程內的線程數還是有限制的,不能無限生成,經驗值在3000~5000左右。
-XX:PermSize=512m -XX:MaxPermSize=512m
持久代一般固定大小為64m,所以增大年輕代后,將會減小年老代大小。此值對系統性能影響較大,Sun官方推薦配置為整個堆的3/8。
設置非堆內存初始值,默認是物理內存的1/64;由XX:MaxPermSize設置最大非堆內存的大小,默認是物理內存的1/4
-XX:+UseConcMarkSweepGC
CMS收集器也被稱為短暫停頓并發收集器。它是對年老代進行垃圾收集的。CMS收集器通過多線程并發進行垃圾回收,盡量減少垃圾收集造成的停頓。CMS收集器對年輕代進行垃圾回收使用的算法和Parallel收集器一樣。這個垃圾收集器適用于不能忍受長時間停頓要求快速響應的應用。
-XX:+UseParNewGC對年輕代采用多線程并行回收,這樣收得快;
-XX:+CMSClassUnloadingEnabled
如果你啟用了CMSClassUnloadingEnabled ,垃圾回收會清理持久代,移除不再使用的classes。這個參數只有在 UseConcMarkSweepGC 也啟用的情況下才有用。
-XX:+DisableExplicitGC禁止System.gc(),免得程序員誤調用gc方法影響性能;
-XX:+UseCMSInitiatingOccupancyOnly
標志來命令JVM不基于運行時收集的數據來啟動CMS垃圾收集周期。而是,當該標志被開啟時,JVM通過CMSInitiatingOccupancyFraction的值進行每一次CMS收集,而不僅僅是第一次。然而,請記住大多數情況下,JVM比我們自己能作出更好的垃圾收集決策。因此,只有當我們充足的理由(比如測試)并且對應用程序產生的對象的生命周期有深刻的認知時,才應該使用該標志。
-XX:CMSInitiatingOccupancyFraction=68
默認CMS是在tenured generation(年老代)占滿68%的時候開始進行CMS收集,如果你的年老代增長不是那么快,并且希望降低CMS次數的話,可以適當調高此值;
-XX:+UseParNewGC:對年輕代采用多線程并行回收,這樣收得快;
-XX:HeapDumpPath
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:/usr/aaa/dump/heap_trace.txt
上面的的參數打Heap Dump信息
-XX:+HeapDumpOnOutOfMemoryError
此參數可以控制OutOfMemoryError時打印堆的信息
大家可能注意到了,這里推薦采用cms方式進行垃圾回收;
CMS是一種以獲取最短回收停頓時間為目標的收集器,可以有效減少服務器停頓的時間;
CMS的GC線程對CPU的占用率會比較高,但在多核的服務器上還是展現了優越的特性,目前也被部署在國內的各大電商網站上。所以這里強烈推薦!
cms的概念:
CMS收集器也被稱為短暫停頓并發收集器。它是對年老代進行垃圾收集的。CMS收集器通過多線程并發進行垃圾回收,盡量減少垃圾收集造成的停頓。CMS收集器對年輕代進行垃圾回收使用的算法和Parallel收集器一樣。這個垃圾收集器適用于不能忍受長時間停頓要求快速響應的應用。CMS采用了多種方式盡可能降低GC的暫停時間,減少用戶程序停頓。停頓時間降低的同時犧牲了CPU吞吐量 。這是在停頓時間和性能間做出的取舍,可以簡單理解為"空間(性能)"換時間。
調整的節奏:
由于怕影響線上應用,所以調整的步驟分三步:
第一步:部分影響少量機器試點,對比未調整的機器,觀察調整后的結果;
第二步:調整部分應用的參數,進行壓測,觀察高并發壓測之后的效果;
第三步:調整部分核心應用的jvm參數,通過818大促來實際檢驗效果;
目前818大促已經結果。正好做一個個總結。
一. 長期表現
第一個變化:fgc的次數減少,減少了大概一倍以上;
mobile工程,調整前基本上一天1-2輛次,調整后基本上就是2-3天一次:
online(另外一個工程):可以明顯看到fgc的統計頻率少了很多;
第二個變化:fgc的時間減少
原來一次fgc要將近500ms,現在只要100ms不到了。
也證明了cms最大的好處就是減少fgc的停頓時間。
二. 壓測及大促表現
fgc的時間基本上是大大縮短,yanggc的時間變長,次數變化不大;
數據來源:測試團隊的壓測總結
| fullgc次數 | 1 | 1 | 1 | |
| fullgc總時間 | 343 | 250 | 1219 | |
| 默認垃圾收集器/CMS fullgc 時間 | 3.55 | 4.88 | CMS fullgc時間比默認垃圾收集器時間明顯要少。 | |
| fullgc時間點 | 2:48:36 | 3:14:36 | 5:30:36 | |
| fullgc時使用率CPU% | 40% | 10% | 16% | |
| fullgc時的load Average | 1.19 | 0.49 | 1.21 | |
| younggc總次數 | 1094 | 1098 | 1078 | |
| younggc總時間 | 44093 | 44632 | 30387 | |
| younggc平均時間 | 40.30 | 40.65 | 28.19 | |
| younggc最大時間 | 1332 | 1268 | 928 | |
| CMS/默認垃圾收集器(younggc總時間) | 1.45 | 1.47 | CMS younggc時間比默認垃圾收集器耗時 | |
| CMS/默認垃圾收集器(younggc平均時間) | 1.43 | 1.44 | CMS younggc時間比默認垃圾收集器耗時 | |
| CMS/默認垃圾收集器(younggc最大時間) | 1.44 | 1.37 | CMS younggc時間比默認垃圾收集器最差情況要差 |
三. 關于哨兵上統計full gc的次數的解釋
哨兵上我們可以安全的說:
Full GC的次數說的是stop the world的次數,所以一次CMS至少會讓Full GC的次數+2,因為CMS Initial mark和remark都會stop the world,記做2次。而CMS可能失敗再引發一次Full GC
如果CMS并發GC過程中出現了concurrent mode failure的話那么接下來就會做一次mark-sweep-compact的full GC,這個是完全stop-the-world的。
正是這個特征,使得CMS的每個并發GC周期總共會更新full GC計數器兩次,initial mark與final re-mark各一次;如果出現concurrent mode failure,則接下來的full GC自己算一次。
四. 遇到的幾個問題
問題一:堆棧溢出;
-Xss256k這個參數調整了,遠濤反饋可能會影響trace的調用。 報如下錯誤:
因為這個參數是設置每個線程的堆棧大小。JDK5.0以后每個線程堆棧大小為1M,以前每個線程堆棧大小為256K。在相同物理內存下,減小這個值能生成更多的線程。
所以今天去掉某臺inventory機器的-Xss256k參數,看一下是不是這個導致的
問題二:初始化標記階段耗時過長:
一般的建議是cms階段兩次STW的時間不超過200ms,如果是CMS Initial mark階段導致的時間過長:
在初始化標記階段(CMS Initial mark),為了最大限度地減少STW的時間開銷,我們可以使用:
-XX:+CMSParallelInitialMarkEnabled
開啟初始標記過程中的并行化,進一步提升初始化標記效率;
問題三:remark階段stw的時間過長
如下圖:
可以采用的方式是:
在CMS GC前啟動一次ygc,目的在于減少old gen對ygc gen的引用,降低remark時的開銷-----一般CMS的GC耗時 80%都在remark階段
-XX:+CMSScavengeBeforeRemark
jmap分析:
問題四:nio框架占用DirectMemory導致的OutOfMemoryError
處理方式:使用XX:+DisableExplicitGC
增加DirectMemory的大小;
1、DirectMemory不屬于java堆內存、分配內存其實是調用操作系統的Os:malloc()函數。
2、容量可通過-XX:MaxDirectMemorySize指定,如果不指定,則默認與Java堆的最大值(-Xmx指定)一樣。注意 ibm jvm默認Direct Memory與-Xmx無直接關系。
3、Direct Memory 內存的使用避免Java堆和Native堆中來回復制數據。從某些場景中提高性能。
4、直接ByteBuffer對象會自動清理本機緩沖區,但這個過程只能作為Java堆GC的一部分來執行,因此它們不會自動響應施加在本機堆上的壓力。
5、GC僅在Java堆被填滿,以至于無法為堆分配請求提供服務時發生,或者在Java應用程序中顯示調用System.gc()函數來釋放內存(一些NIO框架就是用這個方法釋放占用的DirectMemory)。
6、該區域使用不合理,也是會引起OutOfMemoryError。
7、在需要頻繁創建Buffer的場合,由于創建和銷毀DirectBuffer的代價比較高昂,是不宜使用DirectBuffer的,但是如果能將DirectBuffer進行復用,那么 ,在讀寫頻繁的情況下,它完全可以大幅改善性能。(對DirectBuffer的讀寫比普通Buffer快,但是對他的創建和銷毀比普通Buffer慢)。
總結
以上是生活随笔為你收集整理的大型跨境电商 JVM 调优经历的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hive学习笔记 —— Hive的数据类
- 下一篇: 使 IDEA 的 termina l可以