。html
更新記錄:java
學習開源項目的啓動腳本是個不錯的主意,好比Cassandra家的, 附送一篇解釋它的文章。git
JVM調優的"標準參數"的各類陷阱 R大的文章,在JDK6時寫的,期待更新。github
偶然翻到Linkedin工程師的一篇文章。數據庫
更偶然翻到的一份不錯的參數列表。apache
當你在網上興沖沖找到一個可優化的參數時,先用-XX: +PrintFlagsFinal看看,它可能已經默認打開了,再找到一個,仍是默認打開了...數組
JDK7與JDK8,甚至JDK7中的不一樣版本,有些參數值都不同,因此不要輕信網上任何文章,一切以生產環境同版本的JDK打出來的爲準。緩存
常常以相似下面的語句去查看參數,偷懶不起應用,用-version代替。有些參數設置後會影響其餘參數,因此查看時也把它帶上。安全
java -server -Xmx1024m -Xms1024m -XX:+UseConcMarkSweepGC -XX:+PrintFlagsFinal -version| grep ParallelGCThreads服務器
JDK8會默認打開-XX:+TieredCompilation多層編譯,而JDK7則不會。JDK7u40之後的版本會默認打開-XX:+OptimizeStringConcat優化字符串拼接,而以前的則不打開。
對於這些參數,個人建議是順勢而爲,JDK在那個版本默認打開不打開總有它的理由。安全第一,沒有很好的因由,不要隨便由於網上某篇文章的推薦(包括你如今在讀的這篇)就去設置。
先寫一些不那麼常見的,後面再來老生常談。
JDK1.6開始默認打開的偏向鎖,在沒有競爭的狀況下,會取消線程同步的原語,好比那個全部方法都掛着synchronized關鍵字的StringBuffer,若是始終只有一條線程在訪問它,就略過同步操做以得到性能提高。
但一旦有第二條線程訪問這把鎖,JVM就要撤銷偏向鎖恢復到未鎖定線程的狀態,用"-XX:+PrintSafepointStatistics -XX rintSafepointStatisticsCount=1"
能夠看到很多RevokeBiasd的紀錄,像GC同樣,會Stop The World的幹活,雖然只是很短很短的停頓,但對於多線程併發的應用,取消掉它反而有性能的提高和延時的極微的縮短,因此Cassandra就取消了它。
啓動時就把參數裏說好了的內存所有舔一遍,可能令得啓動時慢上一點,但後面訪問時會更流暢,好比頁面會連續分配,好比不會在晉升新生代到老生代時纔去申請頁面使得GC停頓時間加長。不過這選項對32G之類的大堆纔會更有感受一點。
UUID.randomUUID() 有時候會很慢,Thread Dump一看竟然被鎖住了,緣由是裏面用了SecureRandom,要等待機器產生新的噪音(好比機器裏的某個文件發生了變化)才肯產生新的隨機數。所以最好讓熵池裏沒有新的噪音因子時重用當前的因子。詳見 JVM上的隨機數與熵池策略
Integer i = 3;
這語句有着 int自動裝箱成Integer的過程,JDK默認只緩存 -128 ~ +127的int 和 long,超出範圍的數字就要即時構建新的Integer對象。設爲20000後,咱們應用的QPS從48,000提高到50,000,足足4%的影響。詳見Java Integer(-128~127)值的==和equals比較產生的思考
Cassandra家的一個參數,一直沒留意,直到發生高IO時的JVM停頓。原來每次進入安全點(好比GC), JVM都會默默的在/tmp/hperf 目錄寫上一點statistics數據,若是恰好遇到PageCache刷盤,把文件阻塞了,就不能結束這個Stop the World的安全點了。用此參數能夠禁止JVM寫statistics數據,代價是VisualVM和jstat用不了,只能用JMX取數據,但在生產環境原本就不須要VisaulVM。詳見The Four Month Bug: JVM statistics cause garbage collection pauses
-XX:+AggressiveOpts是一些還沒默認打開的優化參數集合, -XX:AutoBoxCacheMax是其中的一項。但如前所述,關鍵系統裏不建議打開。雖然經過-XX:+AggressiveOpts 與 -XX:-AggressiveOpts 的對比,目前才改變了三個參數,但爲免之後某個版本的JDK裏默默改變動多激進的配置,仍是不要了。
Linkined那種黑科技,先要解鎖VMOptions才能配置的就更不用說了,好比
-XX:+UnlockDiagnosticVMOptions -XX: ParGCCardsPerStrideChunk=32768
JIT Compile相關的參數,函數調用多少次以後開始編譯的閥值,內聯函數大小的閥值等等,不要亂改了。
-XX:+UseFastAccessorMethods,聽說在多層編譯下還慢了,因此是默認關閉的。
-server 與 -client的JVM默認參數徹底不同,雖然在Linux 64位JVM裏默認會被認成server模式,但仍是順手寫上吧。
爲了穩健,仍是8G如下的堆仍是CMS好了,G1的細節實現起來難度太大,從理論提出到如今都作了六七年了。
CMS真正可設的東西也很少,詳見JVM實用參數(七)CMS收集器
-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly
由於咱們的監控系統會經過JMX監控內存達到90%的情況(留點處理的時間),因此設置讓它75%就開始跑了,早點開始也能避免Full GC等意外狀況(概念重申,這種主動的CMS GC,和JVM的老生代、永久代、堆外內存徹底不能分配內存了而強制Full GC是不一樣的概念)。爲了讓這個設置生效,還要設置-XX:+UseCMSInitiatingOccupancyOnly,不然75只被用來作開始的參考值,後面仍是JVM本身算。
-XX:MaxTenuringThreshold=2,這是GC裏改動效果最明顯的一個參數了。對象在Survivor區熬過多少次Young GC後晉升到年老代,JDK7裏看起來默認是6,跑起來好像變成了15。
Young GC是最大的應用停頓來源,而新生代裏GC後存活對象的多少又直接影響停頓的時間,因此若是清楚Young GC的執行頻率和應用裏大部分臨時對象的最長生命週期,能夠把它設的更短一點,讓其實不是臨時對象的新生代長期對象趕忙晉升到年老代,別呆着。
用-XX:+PrintTenuringDistribution觀察下,若是後面幾代都差很少,就能夠設小,好比JMeter裏是2。而咱們的兩個系統裏一個設了2,一個設了6。
-XX:+ExplicitGCInvokesConcurrent, 但不要-XX:+DisableExplicitGC, 好比Netty之堆外內存掃盲篇,可見禁了system.gc() 未必是好事,只要本身的代碼裏沒有調它,也沒用什麼特別爛的類庫,真有人調了總有調的緣由。-XX+ExplicitGCInvokesConcurrent 則在full gc時,並不全程停頓,依然只在ygc和兩個remark階段停頓,詳見JVM源碼分析之SystemGC徹底解讀
-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:+CMSScavengeBeforeRemark,默認爲關閉,在CMS remark前,先執行一次minor GC將新生代清掉,這樣從老生代的對象引用到的新生代對象的個數就少了,中止全世界的CMS remark階段就短一些。若是看到GC日誌裏remark階段的時間超長,能夠打開此項看看有沒有效果,不然仍是不要打開了,白白多了次GC。
-XX:CMSFullGCsBeforeCompaction,默認爲0,即每次full gc都對老生代進行碎片整理壓縮。Full GC 不一樣於 前面設置的75%老生代時觸發的CMS GC,只在System.gc(),老生代達到100%,老生代碎片過大沒法分配空間給新晉升的大對象這些特殊狀況裏發生,因此設爲每次都進行碎片整理是合適的,詳見此貼裏R大的解釋。
這些關於大小的參數,給人感受是最踏實可控的。
其實JVM除了顯式設置的-Xmx堆內存,還有一堆其餘佔內存的地方(堆外內存,線程棧,永久代,二進制代碼cache),在容量規劃的時候要留意。
關鍵業務系統的服務器上內存通常都是夠的,因此儘管設得寬鬆點。
-Xmx, -Xms, 堆內存大小,2~4G都可,再大了GC時間會拖長。
-Xmn or -XX:NewSize and -XX:MaxNewSize or -XX:NewRatio, JDK默認新生代佔堆大小的1/3, 我的喜歡把對半分, 增大新生代的大小,能減小GC的頻率(但也會加大每次GC的停頓時間),主要是看老生代裏沒多少長期對象的話,佔2/3太多了。能夠用-Xmn 直接賦值(等於-XX:NewSize and -XX:MaxNewSize同值的縮寫),或把NewRatio設爲1來對半分(但若是想設置新生代比老生代大就只能用-Xmn)。
-XX: PermSize=128m -XX:MaxPermSize=512m (JDK7)如今的應用有Hibernate/Spring這些鬧騰的傢伙AOP以後類都比較多,能夠一開始就把初始值從64M設到128M,並設一個更大的Max值以求保險。
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m(JDK8),JDK8的永生代幾乎可用完機器的全部內存,一樣設一個128M的初始值,512M的最大值保護一下。
-XX:SurvivorRatio 新生代中每一個存活區的大小,默認爲8,即1/10的新生代 1/(SurvivorRatio+2),有人喜歡設小點省點給新生代,但要避免過小使得存活區放不下臨時對象而要晉升到老生代,仍是從GC Log裏看實際狀況了。
-Xss 在堆以外,線程佔用棧內存,默認每條線程爲1M(之前是256K)。除了方法調用出參入參的棧,逃逸分析後也會把只在該線程裏可見的對象直接分配在線程棧裏,而不是公共的Heap裏,也就減小了新生代的GC頻率。有人喜歡設小點節約內存開更多線程,但反正內存夠也就沒必要要設小,有人喜歡再設大點。
-XX:MaxDirectMemorySize,堆外內存/直接內存的大小,默認爲Heap區總內存減去一個Survivor區的大小,詳見Netty之堆外內存掃盲篇。
-XX:ReservedCodeCacheSize, JIT編譯後二進制代碼的存放區,滿了以後就再也不編譯。JDK7默認不開多層編譯48M,開了96M,而JDK8默認開多層編譯256M。能夠在JMX裏看看CodeCache的大小,JDK7下的48M通常夠了,也能夠把它設大點,反正內存多。
JVM輸出的各類日誌,若是未指定路徑,一般會生成到運行應用的相同目錄,爲了不有時候在不一樣的地方執行啓動腳本,通常將日誌路徑集中設到一個固定的地方。
運維有時會對啓動參數作一些臨時的更改,將每次啓動的參數輸出到stdout,未來有據可查。
打印出來的是命令行裏設置了的參數以及由於這些參數隱式影響的參數,好比開了CMS後,-XX:+UseParNewGC也被自動打開。
爲異常設置StackTrace是個昂貴的操做,因此當應用在相同地方拋出相同的異常N次(兩萬?)以後,JVM會對某些特定異常如NPE,數組越界等進行優化,再也不帶上異常棧。此時,你可能會看到日誌裏一條條Null Point Exception,而真正輸出完整棧的日誌早被滾動到不知哪裏去了,也就徹底不知道這NPE發生在什麼地方,欲哭無淚。 因此,將它禁止吧。
JVM crash時,hotspot 會生成一個error文件,提供JVM狀態信息的細節。如前所述,將其輸出到固定目錄,避免到時會處處找這文件。文件名中的%p會被自動替換爲應用的PID
-XX:ErrorFile=${MYLOGDIR}/hs_err_%p.log
固然,更好的作法是生成coredump,從CoreDump可以轉出Heap Dump 和 Thread Dump 還有crash的地方,很是實用。
在啓動腳本里加上 ulimit -c unlimited或其餘的設置方式,若是有root權限,用一下一下輸出目錄更好
echo "/{MYLOGDIR}/coredump.%p" > /proc/sys/kernel/core_pattern
什麼?你不知道這東西什麼用?看來你是沒遇過JVM Segment Fault的幸福人。
在Out Of Memory,JVM快死快死掉的時候,輸出Heap Dump到指定文件。否則開發不少時候還真不知道怎麼重現錯誤。
路徑只指向目錄,JVM會保持文件名的惟一性,叫java_pid${pid}.hprof。若是指向文件,而文件已存在,反而不能寫入。
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOGDIR}/
-Xloggc:/dev/shm/gc-myapplication.log -XX:+PrintGCDateStamps -XX:+PrintGCDetails
詳見JVM實用參數(八)GC日誌,有人擔憂寫GC日誌會影響性能,但測試下來實在沒什麼影響,仍是留一份用來排查好。
到後來,又發現若是趕上高IO的狀況,若是GC的時候,操做系統正在flush pageCache 到磁盤,也可能致使GC log文件被鎖住,從而讓GC結束不了。因此把它指向了/dev/shm 這種內存中數據庫,避免這種停頓,詳見Eliminating Large JVM GC Pauses Caused by Background IO Traffic
另外還有一個-XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime,它的名字沒起好,它除了打印清晰的GC停頓時間外,還能夠打印其餘的停頓時間,好比取消偏向鎖,class 被agent redefine,code deoptimization等等,有助於發現一些原來沒想到的問題,建議也加上。若是真的發現了一些不知什麼的停頓,再加上"-XX:+PrintSafepointStatistics -XX: PrintSafepointStatisticsCount=1"
找緣由。
GC日誌默認會在重啓後清空,但有人擔憂長期運行不重啓的應用會把文件弄得很大,有"-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=1M"的參數可讓日誌滾動起來。但重啓後的文件名太混亂太讓人頭痛,因此仍是不加。
-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,不容許遠程訪問。