關鍵業務系統的JVM啟動參數推薦

jopen 9年前發布 | 19K 次閱讀 JVM Java開發
 

在關鍵的業務系統里,除了繼續追求技術人員最愛的高吞吐與低延時之外,系統的穩定性與出現問題時排查的便捷性也很重要。

這是本文的一個原則,后面也會一次又一次的強調,所以與網上其他的文章略有不同,請JVM調優高手和運維老大們多指引。

前言1,資料

學習開源項目的啟動腳本是個不錯的主意,比如 Cassandra家的 , 附送一篇 解釋它的文章

偶然翻到Linkedin工程師的 一篇文章

更偶然翻到的 一份不錯的參數列表

前言2, -XX:+PrintFlagsFinal打印參數值

當你在網上興沖沖找到一個可優化的參數時,先用-XX: +PrintFlagsFinal看看,它可能已經默認打開了,再找到一個,還是默認打開了....

JDK7與JDK8,甚至JDK7中的不同版本,有些參數值都不一樣,所以不要輕信網上任何文章,一切以生產環境同版本的JDK打出來的為準。

經常以類似下面的語句去查看參數,偷懶不起應用,用-version代替。有些參數設置后會影響其他參數,所以查看時也把它帶上。

java -server -Xmx1024m -Xms1024m -XX:+UseConcMarkSweepGC -XX:+PrintFlagsFinal -version| grep ParallelGCThreads

前言3,關于默認值

JDK8會默認打開-XX:+TieredCompilation多層編譯,而JDK7則不會。JDK7u40以后的版本會默認打開-XX:+OptimizeStringConcat優化字符串拼接,而之前的則不打開。

對于這些參數,我的建議是順勢而為,JDK在那個版本默認打開不打開總有它的理由。安全第一,沒有很好的因由,不要隨便因為網上某篇文章的推薦(包括你現在在讀的這篇)就去設置。

1. 性能篇

先寫一些不那么常見的,后面再來老生常談。

1.1 取消偏向鎖 -XX:-UseBiasedLocking

JDK1.6開始默認打開的偏向鎖,在沒有競爭的情況下,會取消線程同步的原語,比如那個所有方法都掛著synchronized關鍵字的StringBuffer,如果始終只有一條線程在訪問它,就略過同步操作以獲得性能提升。

但一旦有第二個線程訪問這把鎖,JVM就要撤銷偏向鎖恢復到未鎖定線程的狀態,用"-XX:+PrintSafepointStatistics -XX: PrintSafepointStatisticsCount=1"可以看到不少RevokeBiasd的紀錄,像GC一樣,會Stop The World的干活,雖然只是很短很短的停頓,但對于多線程并發的應用,取消掉它反而有性能的提升和延時的極微的縮短,所以Cassandra就取消了它。

1.2 啟動時訪問并置零內存頁面-XX:+AlwaysPreTouch

啟動時就把參數里說好了的內存全部舔一遍,可能令得啟動時慢上一點,但后面訪問時會更流暢,比如頁面會連續分配,比如不會在晉升新生代到老生代時才去申請頁面使得GC停頓時間加長。不過這選項對32G之類的大堆才會更有感覺一點。

1.3 -Djava.security.egd=file:/dev/./urandom

UUID.randomUUID() 有時候會很慢,Thread Dump一看居然被鎖住了,原因是里面用了SecureRandom,要等待機器產生新的噪音(比如機器里的某個文件發生了變化)才肯產生新的隨機數。因此最好讓熵池里沒有新的噪音因子時重用當前的因子。詳見

JVM上的隨機數與熵池策略

1.4 -XX:AutoBoxCacheMax=20000 與 不建議打開的-XX:+AggressiveOpts

Integer i = 3; 這語句有著 int自動裝箱成Integer的過程,JDK默認緩存 -128~ +128的int 和 long,超出范圍的數字就要即時構建新的Integer對象。設為20000后,我們應用的QPS從48,000提升到50,000,足足4%的影響。詳見 Java Integer(-128~127)值的==和equals比較產生的思考

這是-XX:+AggressiveOpts中的其中一項,AggressiveOpts是一些還沒默認打開的優化參數集合。但如前所述,關鍵系統里不建議打開。雖然通過-XX:+AggressiveOpts 與 -XX:-AggressiveOpts 的對比,目前才改變了三個參數,但為免以后某個版本的JDK里默默改變更多激進的配置,還是不要了。

Linkined那種黑科技,先要解鎖VMOptions才能配置的就更不用說了,比如

-XX:+UnlockDiagnosticVMOptions -XX arGCCardsPerStrideChunk=32768

1.5 -server

-server 與 -client的JVM默認參數完全不一樣,雖然在Linux 64位JVM里默認會被認成server模式,但還是順手寫上吧。

1.6 GC策略

為了穩健,還是CMS好了,G1的細節實現起來難度太大,從理論提出到現在都做了六七年了。

CMS真正可設的東西也不多,詳見 JVM實用參數(七)CMS收集器

-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly

因為我們的監控系統會通過JMX監控內存達到90%的狀況(留點處理的時間),所以設置讓它75%就開始跑了。為了讓這個設置生效,還要設置-XX:+UseCMSInitiatingOccupancyOnly,否則75只被用來做開始的參考值,后面還是JVM自己算。

-XX:MaxTenuringThreshold=2,這是GC里改動效果最明顯的一個參數了。對象在Survivor區熬過多少次Young GC后晉升到年老代,JDK7里看起來默認是6,跑起來好像變成了15。

Young GC是最大的應用停頓來源,而新生代里GC后存活對象的多少又直接影響停頓的時間,所以如果清楚Young GC的執行頻率和應用里大部分臨時對象的最長生命周期,可以把它設的更短一點,讓其實不是臨時對象的新生代趕緊晉升到年老代,別呆著。

用-XX:+PrintTenuringDistribution觀察下,如果后面幾代都差不多,就可以設小,比如JMeter里是2,一般6也夠了。

-XX: ParallelRefProcEnabled, 默認為false,并行的處理Reference對象,如WeakReference,除非在GC log里出現Reference處理時間較長的日志,否則效果不會很明顯,但我們總是要JVM盡量的并行,所以設了也就設了。

其他不必設的參數

-XX:+CMSClassUnloadingEnabled,在CMS中清理永久代中的過期的Class而不等到Full GC,JDK7默認關閉而JDK8打開。看自己情況,比如有沒有運行動態語言腳本如Groovy產生大量的臨時類。它會增加CMS remark的暫停時間,所以如果新類加載并不頻繁,這個參數還是不開的好。

用了CMS,新生代收集默認就是-XX:+UseParNewGC,不用自己設。

并發收集線程數,ParallelGCThreads=8+( Processor - 8 ) ( 5/8 ), ConcGCThreads = (ParallelGCThreads + 3)/4,比如雙CPU,六核,超線程就是24個處理器,小于8個處理器時ParallelGCThreads按處理器數量,大于時按上述公式 ParallelGCThreads=18, ConcGCThreads=5。這線程數調整了變化也不大,還是別亂動了。

-XX:+DisableExplicitGC, 詳見 Netty之堆外內存掃盲篇 ,禁了system.gc() 未必是好事,只要自己的代碼里沒有調它,也沒用什么特別爛的類庫,真有人調了總有調的原因。

1.7 大小的設置

這些關于大小的參數,給人感覺是最踏實可控的。

其實JVM除了顯式設置的堆內存,還有一堆其他占內存的地方,在容量規劃的時候要留意。

關鍵業務系統的服務器上內存一般都是夠的,所以盡管設得寬松點。

-Xmx, -Xms, 堆內存大小,2~4G均可,再大了GC時間會拖長。

-Xmn or -XX:NewSize and -XX:MaxNewSize or -XX:NewRatio, JDK默認新生代占堆大小的1/3, 個人喜歡把對半分,用-Xmn 直接賦值(等于-XX:NewSize and -XX:MaxNewSize同值的縮寫),或把NewRatio設為1。 增大新生代的大小,能減少GC的頻率(但也會加大每次GC的停頓時間),主要是看老生代里沒多少長期對象的話,占2/3太多了。

-XX: PermSize=128m -XX:MaxPermSize=512m (JDK7)現在的應用有Hibernate/Spring這些鬧騰的家伙AOP之后類都比較多,可以一開始就把初始值從64M設到128M,并設一個更大的Max值以求保險。

-XX:MetaspaceSize=128m(JDK8),JDK8的永生代幾乎可用完機器的所有內存,同樣設一個128M的初始值。

其他可選項

-XX:SurvivorRatio新生代中每個存活區的大小,默認為8,即1/10的新生代 1/(SurvivorRatio+2),有人喜歡設小點省點給新生代,但要避免太小使得存活區放不下臨時對象而要晉升到老生代,還是從GC Log里看實際情況了。

-Xss在堆之外,線程占用棧內存,默認每條線程為1M(以前是256K)。除了方法調用出參入參的棧,逃逸分析后也會把只在該線程里可見的對象直接分配在線程棧里,而不是公共的Heap里,也就減少了新生代的GC頻率。有人喜歡設小點節約內存開更多線程,但反正內存夠也就不必要設小,有人喜歡再設大點。

-XX:MaxDirectMemorySize,堆外內存/直接內存的大小,默認和堆內存差不多大,詳見 Netty之堆外內存掃盲篇

-XX:ReservedCodeCacheSize, JIT編譯后二進制代碼的存放區,滿了之后就不再編譯。JDK7默認50M,而JDK8開啟了多層編譯所以默認為256M。可以在JMX里看看 CodeCache的大小,JDK7下的50M不開多層編譯一般夠了,也可以把它設大點,反正內存多。

2. 監控篇

JVM輸出的各種日志,如果未指定路徑,通常會生成到運行應用的相同目錄,為了避免有時候在不同的地方執行啟動腳本,一般將日志路徑集中設到一個固定的地方。

2.1 -XX:+PrintCommandLineFlags

運維有時會對啟動參數做一些臨時的更改,將每次啟動的參數輸出到stdout,將來有據可查。

打印出來的是命令行里設置了的參數以及因為這些參數隱式影響的參數,比如開了CMS后,-XX:+UseParNewGC也被自動打開。

2.2 -XX:-OmitStackTraceInFastThrow

為異常設置StackTrace是個昂貴的操作,所以當應用在相同地方拋出相同的異常N次(兩萬?)之后,JVM就會優化,只輸出異常信息而不帶異常棧。此時,你可能會看到日志里一條條Null Point Exception,而真正輸出完整棧的日志早被滾動到不知哪里去了,也就完全不知道這NPE發生在什么地方,欲哭無淚。 所以,將它禁止吧。

2.3 coredump與 -XX:ErrorFile=${MYLOGDIR}/hs_err_%p.log

JVM crash時,hotspot 會生成一個error文件,提供JVM狀態信息的細節。如前所述,將其輸出到固定目錄,避免到時會到處找這文件。文件名中的%p會被自動替換為應用的PID

當然,更好的做法是生成coredump,從CoreDump能夠轉出Heap Dump 和 Thread Dump 還有crash的地方,非常實用。

在啟動腳本里加上 ulimit -c unlimited或其他的設置方式,如果有root權限,用一下一下輸出目錄更好

echo "/{MYLOGDIR}/coredump.%p" > /proc/sys/kernel/core_pattern

什么?你不知道這東西什么用?看來你是沒遇過JVM Segment Fault的幸福人。

2.4 -XX:+HeapDumpOnOutOfMemoryError

在Out Of Memory,JVM快死快死掉的時候,輸出Heap Dump到指定文件。不然開發很多時候還真不知道怎么重現錯誤。

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOGDIR}/oom.hprof

2.5 GC日志

-Xloggc:${LOGDIR}/gc-$SERVER_API.log -XX:+PrintGCDateStamps -XX:+PrintGCDetails

詳見 JVM實用參數(八)GC日志 ,有人擔心寫GC日志會影響性能,但測試下來實在沒什么影響,還是留一份用來排查好。

另外,“-XX:+PrintGCApplicationStoppedTime” 還可以打印除GC外的停頓時間,比如取消偏向鎖,instrumentation,code deoptimization等。

GC日志默認會在重啟后清空,但有人擔心長期運行不重啟的應用會把文件弄得很大,有些-XX:+UseGCLogFileRotation相關的參數可以讓日志滾動起來。但重啟后的文件名太混亂太讓人頭痛,所以還是不加。

2.6 JMX

-Dcom.sun.management.jmxremote.port=${MY_JMX_PORT} -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1

以上設置,只讓本地的Zabbix之類監控軟件通過JMX監控JVM,不允許遠程訪問。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!