目錄
jvm crash & heap dump & core dump & High CPU
模擬jvm crash使用CrashAnalysis幫助分析crash 文件,簡單例子:
模擬OOM使用eclipse MAT分析heap dump文件,簡單例子:
3) jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]
JVM性能調優是一個長期的循序漸進的過程,是一個有深度且有廣度的課題。不同的應用場景適用於不同的優化方案,本文只通過內存優化、GC垃圾收集器優化、線程調優、JIT編譯器調優等四個方向提供一些相關的優化方法。並且會持續的豐富下去。如有錯誤的地方,非常希望能夠一起探討、學習、進步。本文只針對JDK8及後續版本,默認運行在server模式下。本文僅限於Oracle hotspot的jvm層面調優和生產環境的問題排查,並不會涉及到代碼層面編寫的各種工作。
注:閱讀本文前需要對JMM、OOM、GC收集器以及GC收集算法、多線程與鎖或無鎖CAS(這裏的線程都是用戶線程,但可以幫助區分和理解本文中所說的工作線程也就是JVM工作線程)、JIT編譯器的運作方式有一定的瞭解。
JMM參考資料:
https://blog.csdn.net/javazejian/article/details/72772461
OOM參考資料:
https://blog.csdn.net/sunquan291/article/details/79109197
GC收集器參考資料:
https://blog.csdn.net/jl19861101/article/details/88169405
https://blog.csdn.net/jl19861101/article/details/88566316
多線程參考資料:
https://blog.csdn.net/jl19861101/article/details/87357979
https://blog.csdn.net/javazejian/article/details/72828483
https://blog.csdn.net/javazejian/article/details/72772470
https://blog.csdn.net/javazejian/article/details/75043422
JIT參考資料:
https://blog.csdn.net/jl19861101/article/details/88667402
https://blog.csdn.net/u013678930/article/details/52042266
----------------------------------------------------------------------------------
或參考《深入理解Java虛擬機_JVM高級特性與最佳實踐 第2版》《實戰Java虛擬機:JVM故障診斷與性能優化》
內存調優
內存調優分爲堆內存調優與非堆內存調優
jvm堆內存分配示意圖:
對象內存分配流程:
通過命令jhsdb jmap --heap --pid <pid>或者jmap -heap <pid>查看heap情況
一、通用優化
- -XX:-RestrictContended,開關參數。默認開啓。關閉此參數可以消除僞共享參數,但需要配合@sun.misc.Contended註解纔會生效。
- -XX:+UseCompressedOops,開關參數。默認開啓。該參數指定是否啓用指針壓縮優化方式。指針壓縮優化方式可以減小內存佔用。
- -XX:-UseLargePages,開關參數。默認關閉。該參數指定是否支持大內存頁優化。
測試linux是否支持largepage:
# cat /proc/meminfo | grep Huge
HugePages_Total: 0
HugePages_Free: 0
Hugepagesize: 2048 kB
如果有HugePage字樣的輸出內容,說明OS是支持大內存分頁的。Hugepagesize就是默認的大內存頁size。接下來,爲了讓JVM可以調整大內存頁size,需要設置下OS 共享內存段最大值和大內存頁數量。
共享內存段最大值,建議這個值大於Java Heap size,這個例子裏設置了4G內存。
$ echo 4294967295 > /proc/sys/kernel/shmmax
大內存頁數量,這個值一般是 Java進程佔用最大內存/單個頁的大小,比如java設置1.5G,單個頁10M,那麼數量爲1536/10 = 154。
$ echo 154 > /proc/sys/vm/nr_hugepages
- -XX:LargePageSizeInBytes,數值參數。該參數指定單個頁大小。
二、堆內存調優涉及參數
- -Xmx:參數設置JVM最大可用堆(單位k,m,g)。表示java虛擬機堆區內存可被分配的最大上限,通常爲操作系統可用內存的1/4大小。但是開發過程中,通常會將 -Xms 與 -Xmx兩個參數的配置相同的值,其目的是爲了能夠在java垃圾回收機制清理完堆區後不需要重新分隔計算堆區的大小而浪費資源。
- -Xms:參數設置JVM初始堆大小(單位k,m,g)。表示java虛擬機堆區內存初始內存分配的大小,通常爲操作系統可用內存的1/64大小即可,但仍需按照實際情況進行分配。有可能真的按照這樣的一個規則分配時,設計出的軟件還沒有能夠運行得起來就掛了。
- -Xmn:新生代大小(單位k,m,g)。可以通過這個值得到老年代大小:-Xmx減去-Xmn。該參數設置以後-XX:NewSize、-XX:MaxNewSize:都會等於該值。
- -XX:NewSize:初始新生代大小。
- -XX:MaxNewSize:最大新生代大小。
- -XX:NewRatio:設置年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)。等同於設置-XX:MaxNewSize。
- -XX:SurvivorRatio:Eden/Survivor的值. 這個值的說明, 很多網上轉載的都是錯的. 8表示Survivor:Eden=1:8, 因爲survivor區有2個, 所以Eden的佔比爲8/10. Ratio of eden/survivor space size. -XX:SurvivorRatio=6 sets the ratio between each survivor space and eden to be 1:6, each survivor space will be one eighth of the young generation.
- -XX:MaxHeapFreeRatio:GC後如果發現空閒堆內存佔到整個預估堆內存的N%(百分比),則收縮堆內存的預估最大值, 預估堆內存是堆大小動態調控的重要選項之一. 堆內存預估最大值一定小於或等於固定最大值(-Xmx指定的數值). 前者會根據使用情況動態調大或縮小, 以提高GC回收的效率
- -XX:MinHeapFreeRatio:GC後如果發現空閒堆內存佔到整個預估堆內存的N%(百分比), 則放大堆內存的預估最大值
- -XX:MaxTenuringThreshold:默認值15。用來設置多少次Minor GC後對象會進入Old區域。晉升年齡最大閾值,默認15。在新生代中對象存活次數(經過YGC的次數)後仍然存活,就會晉升到老年代。每經過一次YGC,年齡加1,當survivor區的對象年齡達到TenuringThreshold時,表示該對象是長存活對象,就會直接晉升到老年代。
- -XX:TargetSurvivorRatio:設定survivor區的目標使用率。默認50,即survivor區對象目標使用率爲50%。
- -XX:+UseTLAB:開關參數。啓動TLAB。默認啓動。
- -XX:TLABWasteTargetPercent:設置TLAB空間所佔用Eden空間的百分比大小。
- -XX:TLABRefillWasteFraction:設置維護進入TLAB空間單個對象大小,比例值,默認1/64,對象大於該值會去堆創建。
- -XX:+ResizeTLAB:開關參數,默認開啓。默認情況下TLAB和RefillWaste大小會在運行時不斷調整的,使系統的運行狀態達到最優。如果想要禁用自動調整TLAB的大小,可以使用-XX:-ResizeTLAB禁用ResizeTLAB,並使用-XX:TLABSize手工指定一個TLAB的大小。
- -XX:TLABSize:手動指定TLAB大小。默認0。
- -XX:MinTLABSize:最小TLAB大小。默認2048。
- -XX:-AlwaysPreTouch:開關參數。默認關閉。啓動時就把參數裏說好了的內存全部舔一遍,可能令得啓動時慢上一點,但後面訪問時會更流暢,比如頁面會連續分配,比如不會在晉升新生代到老生代時纔去訪問頁面使得GC停頓時間加長。不過這選項對大堆纔會更有感覺一點。(參考資料:https://docs.oracle.com/javase/9/gctuning/garbage-first-garbage-collector-tuning.htm#JSGCT-GUID-4914A8D4-DE41-4250-B68E-816B58D4E278)
- -XX:YoungGenerationSizeIncrement數值參數,默認20。虛擬機動態調整年輕代增加比率。默認20%。
- -XX:TenuredGenerationSizeIncrement數值參數,默認20。虛擬機動態調整老年代的增加比率。默認20%。
- -XX:AdaptiveSizeDecrementScaleFactor數值參數,默認4。虛擬機動態調整空間的收縮比率。如果增長比率是20%。那麼收縮比率=20/4=5%。那麼空間收縮比率爲5%。
注意:JVM會將每個對象的年齡信息、各個年齡段對象的總大小記錄在“age table”表中。基於“age table”、survivor區大小、survivor區目標使用率(-XX:TargetSurvivorRatio)、晉升年齡閾值(-XX:MaxTenuringThreshold),JVM會動態的計算tenuring threshold的值。一旦對象年齡達到了tenuring threshold就會晉升到老年代。
爲什麼要動態的計算tenuring threshold的值呢?假設有很多年齡還未達到TenuringThreshold的對象依舊停留在survivor區,這樣不利於新對象從eden晉升到survivor。因此設置survivor區的目標使用率,當使用率達到時重新調整TenuringThreshold值,讓對象儘早的去old區。
內容來源:https://blog.csdn.net/zero__007/article/details/52797684可以參考:https://blog.csdn.net/foolishandstupid/article/details/77596050
三、非堆內存調優涉及參數
- -Xss:設置每個用戶線程的堆棧大小。
- -XX:+EliminateAllocations 啓用標量替換,允許對象打散分配到棧上。此參數需要配合DoEscapeAnalysis參數使用。
- -XX:AutoBoxCacheMax=128。默認值128。Integer i = 3;這語句有着 int自動裝箱成Integer的過程,JDK默認只緩存 -128 ~ +127的int 和 long,超出範圍的數字就要即時構建新的Integer對象。
- -XX:MetaspaceSize:分配給類元數據空間的初始大小(Oracle邏輯存儲上的初始高水位,the initial high-water-mark ). 此值爲估計值. MetaspaceSize設置得過大會延長垃圾回收時間. 垃圾回收過後, 引起下一次垃圾回收的類元數據空間的大小可能會變大
- -XX:MaxMetaspaceSize:是分配給類元數據空間的最大值, 超過此值就會觸發Full GC. 此值僅受限於系統內存的大小, JVM會動態地改變此值
- -XX:MinMetaspaceFreeRatio:默認值40。表示一次GC以後,爲了避免增加元數據空間的大小,空閒的類元數據的容量的最小比例,不夠就會導致垃圾回收。
- -XX:MaxMetaspaceFreeRatio:默認值70。表示一次GC以後,爲了避免增加元數據空間的大小,空閒的類元數據的容量的最大比例,不夠就會導致垃圾回收。
- -XX:InitialCodeCacheSize:初始JIT編譯器本地機器碼緩存區大小。初始化大小不應該小於系統最小內存頁大小。
- -XX:ReservedCodeCacheSize:(單位k,m,g)默認240M,如果使用-XX:-TieredCompilation參數禁用分層編譯,則默認大小爲48M。本地機器碼緩衝區大小。JIT編譯器編譯後的機器碼保存在內存中,他的大小由此參數控制。一旦代碼緩存耗盡,JIT就會停止,並且在整個虛擬機的聲明週期中,不會再啓動了。這個參數最大可設置2GB,超過此限制將會產生錯誤(Invalid ReservedCodeCacheSize=2049M. Must be at most 2048M.Error: Could not create the Java Virtual Machine.Error: A fatal exception has occurred. Program will exit.)。
- -XX:SegmentedCodeCache:開關參數,默認開啓。JDK9提供的本地代碼緩衝區分段緩存功能。和原來的單一緩存區域不同的是, 新的代碼緩存根據代碼自身的生命週期而分爲三種:永駐代碼(non-method code),短期代碼(profiled code),長期代碼(Non-profiled code)。緩存分段會在各個方面提升程序的性能, 比如做垃圾回收掃描的時候可以直接跳過非方法代碼(永駐代碼), 從而提升效率。
- -XX:MaxDirectMemorySize:直接內存最大值。即NIO進行操作時,可以分配的最大緩存值,默認和heap最大值一致。
- -XX:CompressedClassSpaceSize:類指針壓縮空間大小, 默認爲1G
注意:以下結論在JDK11.0.2_LTS版本中測試所得。其餘版本並沒有測試過。所以不同的版本也有可能會有不同的結論。
1)參數設置存在先後順序。以-Xmn、-XX:NewSize、-XX:MaxNewSize爲例。
-Xmn等同於-XX:NewSize、-XX:MaxNewSize的設置。如果設置了-Xmn的值,NewSize與MaxNewSize的值都會等於該值。但是如果三個參數同時設置了就會導致少許的混亂。jvm會按照參數編寫的先後順序設置系統值。例如參數編寫順序爲:-XX:MaxNewSize=256m -Xmn512m -XX:NewSize=256m。那麼得到的將會是:
由於先設置的MaxNewSize的值倍Xmn的值覆蓋,所以MaxNewSize等於512m。由於最後設置了NewSize也就是初始新生代大小,所以最終只改變了NewSize的值。
2)參數設置存在優先級問題。以-XX:NewSize、-XX:MaxNewSize、-Xmn與--XX:NewRatio爲例。NewSize、MaxNewSize、Xmn的優先級高於RewRatio的設置。如果前三個參數其中任何一個與NewRatio同時存在於一個同一個jvm進程啓動參數中的話。NewRatio設置參數值將會失效。
所以jvm啓動參數請不要混亂使用,須對各參數理解其功能特性,否則會導致後續或者其他jvm調優人員解讀起來產生歧義或產生錯誤的判斷。
GC調優
一、通用調優參數
- -XX:+DisableExplicitGC 開關參數,開啓此參數會禁用代碼中的System.gc(),由於該方法默認會觸發 FGC,並且忽略參數中的 UseG1GC 和 UseConcMarkSweepGC,因此必要時可以禁用該方法。
- -XX:+ExplicitGCInvokesConcurrent 開啓此該參數可以改變代碼中System.gc()的行爲,也就是說,System.gc() 後不使用 FGC ,而是使用配置的併發收集器進行併發收集。注意:使用此選項就不要 使用DisableExplicitGC參數。
- -XX:PretenureSizeThreshold=n 除了年齡外,對象的體積也是影響晉升的一個關鍵,也就是大對象。如果一個對象新生代放不下,只能直接通過分配擔保機制進入老年代。該參數是設置對象直接晉升到老年代的閾值,單位是字節。只要對象的大小大於此閾值,就會直接繞過新生代,直接進入老年代。注意:如果想使這個參數生效,請關閉UseAdaptiveSizePolicy參數。
- -XX:+ScavengeBeforeFullGC開關參數。在執行Full GC之前本身不會進行Minor GC,我們可以配置-XX:+ScavengeBeforeFullGC,讓jvm進行Full GC之前先進行一次Minor GC。
- -XX:ParGCCardsPerStrideChunk=256 該參數控制GC工作線程的任務粒度。在Java中,一個card的大小是512 bytes,而ParGCCardsPerStrideChunk默認的值是256。那麼一個Stride,或者MSDN文章中說的block,就是512 * 256 = 128K。假如你有4G的old gen,就會有4G / 128K = 32K的Strides在young gen collection的時候去掃描。這也就是爲什麼我前面說LinkedIn文章的interesting有些大驚小怪,呃,算法本來就是這個樣子的啊。隨着old gen大小增加,young gen collection就是要做更多的事情。ParGCCardsPerStrideChunk設置成多大,這個並不是僅僅由old gen的大小決定的,更大的決定因素是你的系統。有的文章也給出了32768的值,還有待驗證。(該參數爲診斷參數,需要-XX:+UnlockDiagnosticVMOptions參數來打開此隱藏參數)
- -XX:+UseGCOverheadLimit,開關參數。默認啓動。如果在垃圾收集(GC)中花費的時間太多,則垃圾收集器將拋出OutOfMemoryError:如果在垃圾收集中花費的總時間超過98%,並且回收的堆少於2%,則將拋出OutOfMemoryError。
- -XX:UseAdaptiveSizePolicy,開關參數。默認開啓。開關打開後就不需要手工指定新生代的大小(-Xmm)、Eden與Servivor區的比例(-XX:SurvivorRatio)、晉升老年代對象年齡(-XX:PretenureSizeThreshold)等細節參數。虛擬機會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或者最大的吞吐量,這種調節方式成爲GC自適應的調劑策略。這也是Parallel Scavenge收集器與ParNew收集器的一個重要區別。
- -XX:ParallelRefProcEnabled,開關參數。該參數指定是否並行處理Reference對象。
二、垃圾收集器的組合策略
- -XX:+UseSerialGC參數可指定新生代Serial收集器,老年代Serial Old收集器。
- -XX:+UseParNewGC:新生代使用ParNew收集器,老年代使用Serial Old收集器。(請不要在JDK1.8版本後續使用此參數)
- -XX:+UseConcMarkSweepGC:新生代使用ParNew收集器,老年代使用CMS收集器。
- -XX:+UseParallelGC:新生代使用ParallelGC收集器,老年代使用Serial Old收集器。
- -XX:+UseParallelOldGC:新生代使用ParallelGC收集器,老年代使用ParallelOldGC收集器。
- -XX:+UseG1GC:開關參數。用來開啓和關閉G1收集器。
- -XX:+UnlockExperimentalVMOptions -XX:+UseZGC:參數組合可開啓ZGC垃圾收集器。(注意:此爲JDK11開始提供參數。目前ZGC是實驗版本,而且只適合在Linux環境下使用)
三、各垃圾收集器調優
1. Serial收集器
截止目前JDK11版本。目前沒有相關調優參數
2. ParNew收集器
- -XX:ParallelGCThreads={value} 這個參數是指定STW工作線程的數量,一般最好和 CPU 核心數量相當。默認情況下,當 CPU 數量小於8, ParallelGCThreads 的值等於 CPU 數量,當 CPU 數量大於 8 時,則使用公式:3+((5*CPU)/ 8);同時這個參數只要是並行 GC 都可以使用,不只是 ParNew。
3. Parallel Scavenge收集器
- -XX:ParallelGCThreads={value} 這個參數是指定STW工作線程的數量,一般最好和 CPU 核心數量相當。默認情況下,當 CPU 數量小於8, ParallelGCThreads 的值等於 CPU 數量,當 CPU 數量大於 8 時,則使用公式:3+((5*CPU)/ 8);同時這個參數只要是並行 GC 都可以使用。
- -XX:MaxGCPauseMillis 設置最大垃圾收集停頓時間,他的值是一個大於0的整數。ParallelGC 工作時,會調整 Java 堆大小或者其他的一些參數,儘可能的把停頓時間控制在 MaxGCPauseMillis 以內。如果爲了將停頓時間設置的很小,將此值也設置的很小,那麼 Parallel Scavenge 將會把堆設置的也很小,這將會導致頻繁 GC ,雖然系統停頓時間小了,但總吞吐量下降了。
- -XX:GCTimeRatio 設置吞吐量大小,他的值是一個0 到100之間的整數,假設 GCTimeRatio 的值是 n ,那麼系統將花費不超過 1/(1+n) 的時間用於垃圾收集,即不超過1% 的時間用於垃圾收集。
4. CMS收集器
- -XX:ConcGCThreads=n 設置併發標記線程數。將n設置爲大約並行垃圾收集線程數(ParallelGCThreads)的1/4。(注意:網上其他文章把此參數定義爲與ParallelGCThreads同樣的設置結果是錯誤的。參考資料:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html與https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE與https://www.oracle.com/technetwork/cn/articles/java/g1gc-1984535-zhs.html。官方對於兩個參數給出的是不同的解釋。也可能是我的理解有誤,此結論僅僅只是在官方文檔中找到的答案,但並未在openjdk源碼中得到證實)
- -XX:ParallelGCThreads={value} 這個參數是指定STW工作線程的數量,一般最好和 CPU 核心數量相當。默認情況下,當 CPU 數量小於8, ParallelGCThreads 的值等於 CPU 數量,當 CPU 數量大於 8 時,則使用公式:3+((5*CPU)/ 8);同時這個參數只要是並行 GC 都可以使用。
- -XX:+CMSPrecleaningEnabled,CMS預清理階段開關參數。
- -XX:CMSInitiatingOccupancyFraction是一個0~100之間的整數。用來標記老年代使用比例達到某個比例時觸發CMS垃圾收集器。JDK1.5默認值68。表示老年代使用空間達到68%時CMS垃圾收集器會被激活。如果老年代內存增長很快,建議降低閾值,避免 FGC,如果增長慢,則可以加大閾值,減少 CMS GC 次數。提高吞吐量。如果該值設置爲-1的話,則由另外兩個參數決定啓動CMS收集器MinHeapFreeRatio、CMSTriggerRatio。((100 - MinHeapFreeRatio) + (double)( CMSTriggerRatio * MinHeapFreeRatio) / 100.0) / 100.0 。
- -XX:+UseCMSCompactAtFullCollection 由於 CMS 使用標記清理算法,內存碎片無法避免。該參數指定每次 CMS 後進行一次碎片整理。(JDK8還在使用JDK9開始刪除此參數)
- -XX:CMSFullGCsBeforeCompaction 由於每次進行碎片整理將會影響性能,你可以使用該參數設定多少次 CMS 後才進行一次碎片整理,也就是內存壓縮。(JDK8還在使用JDK9開始刪除此參數)
- -XX:+CMSClassUnloadingEnabled 允許對類元數(舊版本JVM中的永久代,JDK8版本以後的Metaspace區域)據進行回收。它會增加CMS remark的暫停時間,所以如果新類加載並不頻繁,這個參數還是不開的好。
- -XX:+UseCMSInitiatingOccupancyOnly 開啓此參數會關閉CMS自動計算垃圾收集機制。並且完全按照CMSInitiatingOccupancyFraction設置的值來啓動CMS收集器。如果關閉此參數,CMSInitiatingOccupancyFraction的值則會變成一個參考值。
- -XX:CMSWaitDuration 數值參數,單位毫秒,默認值2000。此參數定義間隔N毫秒檢測一次是否需要啓動CMS垃圾收集。
- -XX:-CMSScavengeBeforeRemark開關參數。開啓或關閉在CMS重新標記階段之前的清除(YGC)嘗試,CMS併發標記階段與用戶線程併發進行,此階段會產生已經被標記了的對象又發生變化的情況,若打開此開關,可在一定程度上降低CMS重新標記階段對上述“又發生變化”對象的掃描時間,當然,“清除嘗試”也會消耗一些時間。
5. G1收集器
G1收集器的詳細介紹請參閱:JVM Garbage First(G1)
調優參數:
- -XX:GCTimeRatio 設置吞吐量大小,他的值是一個0 到100之間的整數,假設 GCTimeRatio 的值是 n ,那麼系統將花費不超過 1/(1+n) 的時間用於垃圾收集,即不超過1% 的時間用於垃圾收集。
- -XX:MaxGCPauseMillis=200默認值200毫秒。用於指定最大停頓時間,如果任何一次停頓超過這個設置值時,G1 就會嘗試調整新生代和老年代的比例,調整堆大小,調整晉升年齡的手段,試圖達到目標。和 PS 一樣,停頓時間小了,對應的吞吐量也會變小。這點值得注意。使用MaxGCPauseMillis設置最大停頓時間,G1垃圾收集器使用一個預測模型來決定多少垃圾收集工作可以在最大停頓時間內完成。在垃圾收集工作完成的時候G1自動選擇下一個需要垃圾收集目標也就是收集集合(Collect Set)。(官方給出的解釋是,爲了使G1能夠盡最大可能的保證該參數最大停頓時間的工作性能,儘量不要手動設置新生代大小)
- -XX:GCPauseIntervalMillis 設置STW的間隔時間。
- -XX:G1HeapRegionSize=n 當使用G1收集器時,設置一個region區域的大小。這個大小範圍在1M到32M之間,必須是2的次方。
- -XX:G1NewSizePercent=5 年輕代在堆中最小百分比,默認值是 5%(這是一個實驗性參數experimental,如果使用需要附加參數-XX:+UnlockExperimentalVMOptions)
- -XX:G1MaxNewSizePercent=60 年輕代在堆中的最大百分比,默認值是 60%(這是一個實驗性參數experimental,如果使用需要附加參數-XX:+UnlockExperimentalVMOptions)
- -XX:ParallelGCThreads={value} 這個參數是指定STW工作線程的數量,一般最好和 CPU 核心數量相當。默認情況下,當 CPU 數量小於8, ParallelGCThreads 的值等於 CPU 數量,當 CPU 數量大於 8 時,則使用公式:3+((5*CPU)/ 8);同時這個參數只要是並行 GC 都可以使用。
- -XX:ConcGCThreads=n 設置併發標記線程數。將n設置爲大約並行垃圾收集線程數(ParallelGCThreads)的1/4。
- -XX:InitiatingHeapOccupancyPercent=45,數值參數。該參數決定了IHOP的初始值,IHOP可以決定當整個堆使用率達到多少時,觸發併發標記階段的執行。默認值時45,即當堆的使用率達到45%,執行併發標記階段,該值一旦設置,始終都不會被 G1修改。也就是說,G1 就算爲了滿足 MaxGCPauseMillis 也不會修改此值。如果該值設置的很大,導致併發週期遲遲得不到啓動,那麼引起 FGC 的機率將會變大。如果過小,則會頻繁標記,GC 線程搶佔應用程序CPU 資源,性能將會下降。
- -XX:G1MixedGCLiveThresholdPercent=85 爲混合垃圾回收週期中要包括的老年代區域設置佔用率閾值。默認佔用率爲 85%。(這是一個實驗性參數experimental,如果使用需要附加參數-XX:+UnlockExperimentalVMOptions)
- -XX:G1HeapWastePercent=5 該參數設置您願意浪費的堆百分比。如果可回收百分比小於堆廢物百分比,Java HotSpot VM 不會啓動混合垃圾回收週期。
- -XX:G1MixedGCCountTarget=8 該參數設置標記週期完成後,對存活數據上限爲
G1MixedGCLIveThresholdPercent
的舊區域執行混合垃圾回收的目標次數。默認值是 8 次混合垃圾回收。混合回收的目標是要控制在此目標次數以內。 - -XX:G1OldCSetRegionThresholdPercent=10 該參數設置一個混合收集週期回收老年區域的回收上限。默認值是 Java 堆的 10%。(這是一個實驗性參數experimental,如果使用需要附加參數-XX:+UnlockExperimentalVMOptions)
- -XX:G1ReservePercent=10 設置作爲空閒空間的預留內存百分比,以降低目標空間溢出的風險。默認值是 10%。增加或減少百分比時,請確保對總的 Java 堆調整相同的量。
- -XX:G1RSetUpdatingPauseTimePercent=10 改參數代表G1修改RSet時間百分比。默認是目標停頓時間的10%。
- -XX:+G1EagerReclaimHumongousObjects,開關參數。默認開啓。該參數指示了回收大對象(Humongous Objects)的時機。開啓該參數,虛擬機會在任何STW的適當時候回收大對象。關閉該參數,虛擬機只會在Cleanup pause後與Full GC期間回收大對象。
- -XX:+G1UseAdaptiveIHOP,開關參數。默認開啓。該參數表示是否啓動自適應IHOP。如果啓動自適應IHOP,虛擬機會在運行時自動調整IHOP值,也就是啓動初始標記的閾值。
- -XX:+ReduceInitialCardMarks,開關參數。默認開啓。分配對象時候的一個優化參數。在G1下如果分配較多的大對象。很可能造成停頓時間加長。可以嘗試禁用此選項。慎用。
- -XX:G1RSetRegionEntries,數值參數。增大這個值可以有效的減少RSet的壓縮coarsening的發生機率。
- -XX:ReferencesPerThread,數值參數。該參數決定了併發度:每N個Reference對象都會有一個線程參與Reference處理,這個值受限於-XX:ParallelGCThreads。如果設置爲0,標明最大線程數總是-XX:ParallelGCThreads。這個決定了對 java.lang.Ref.* 這些對象實例的處理是否是多個線程並行執行的。
建議:
評估和微調 G1 GC 時,請記住以下建議:
- 年輕代大小:避免使用
-Xmn
選項或-XX:NewRatio
等其他相關選項手動設置年輕代大小。固定年輕代的大小會覆蓋暫停時間目標(MaxGCPauseMillis)。 - 暫停時間目標:每當對垃圾回收進行評估或調優時,都會涉及到延遲與吞吐量的權衡。G1 GC 是增量垃圾回收器,暫停統一,同時應用程序線程的開銷也更多。G1 GC 的吞吐量目標是 90% 的應用程序時間和 10%的垃圾回收時間。如果將其與 Java HotSpot VM 的吞吐量回收器相比較,目標則是 99% 的應用程序時間和 1% 的垃圾回收時間。因此,當您評估 G1 GC 的吞吐量時,暫停時間目標不要太嚴苛。目標太過嚴苛表示您願意承受更多的垃圾回收開銷,而這會直接影響到吞吐量。當您評估 G1 GC 的延遲時,請設置所需的(軟)實時目標,G1 GC 會盡量滿足。副作用是,吞吐量可能會受到影響。
- 掌握混合垃圾回收:當您調優混合垃圾回收時,請嘗試以下選項。
-XX:InitiatingHeapOccupancyPercent
用於更改標記閾值。-XX:G1MixedGCLiveThresholdPercent
和-XX:G1HeapWastePercent
當您想要更改混合垃圾回收決定時。-XX:G1MixedGCCountTarget
和-XX:G1OldCSetRegionThresholdPercent
當您想要調整舊區域的 CSet 時。
多線程調優
通用調優
- -XX:ThreadStackSize 設置每個用戶線程的堆棧大小。該參數與-Xss參數的設置效果是一樣的。如果兩個參數設置爲0,則虛擬機自動設置爲默認值,(此爲各不同平臺默認值:Linux/ARM (32-bit): 320 KB、Linux/i386 (32-bit): 320 KB、Linux/x64 (64-bit): 1024 KB、OS X (64-bit): 1024 KB、Oracle Solaris/i386 (32-bit): 320 KB、Oracle Solaris/x64 (64-bit): 1024 KB)
- -XX:ParallelGCThreads={value} 這個參數是垃圾收集器STW工作線程的數量,一般最好和 CPU 核心數量相當。默認情況下,當 CPU 數量小於8, ParallelGCThreads 的值等於 CPU 數量,當 CPU 數量大於 8 時,則使用公式:3+((5*CPU)/ 8);同時這個參數只要是並行 GC 都可以使用。
- -XX:ConcGCThreads=n 垃圾收集器併發標記階段線程數。將n設置爲大約並行垃圾收集線程數(ParallelGCThreads)的1/4。
注意:下面參數中英文資料太少。只找到下面這些很少的資料。但可以確定與GC垃圾收集工作線程有關係。具體的相關實現需要進一步查證。(中文資料https://www.cnblogs.com/leonxyzh/p/7484212.html ,https://www.jianshu.com/p/debef4f69a90英文資料https://engineering.linkedin.com/garbage-collection/garbage-collection-optimization-high-throughput-and-low-latency-java-applications 資料來源並非官方文檔,而是LinkedIn在產品迭代中的實踐結論)
- -XX:VMThreadStackSize 虛擬機內部線程大小。比如說STW線程或Concurrent Mark線程。
- -XX:CompilerThreadStackSize 虛擬機內部編譯線程大小。
- -XX:ParGCStridesPerThread=2 控制每次worker thread整幾個Strides (改參數爲診斷參數,需要-XX:+UnlockDiagnosticVMOptions參數來打開此隱藏參數)。
- -XX:-BindGCTaskThreadsToCPUs 開關參數,默認關閉。在操作系統允許的情況下綁定GC線程到單個CPU。開啓該參數可減少thread的switching。
- -XX:-UseGCTaskAffinity 開關參數,默認關閉。開啓該參數保證每次worker thread被喚醒的時候爭取拿到上次沒有完成的工作。(該選項在JDK7和JDK8版本好像不起作用。參考文章:https://cloud.tencent.com/developer/article/1061456)
注意:下面參數與StackOverflowError異常有關係。
- -XX:StackShadowPages=7 一般只有用到本地方法時纔會去修改這個值,或則默認值已經能夠滿足需求,它具體指本地方法與JAVA共享棧,這個值是指在內存單元上,JAVA能夠夠調用本地方法的最大範圍,而這個StackShadowPages 就是這個範圍的最大值,一般地如果改變這個值,需要同時修改-Xss 或者-XX:ThreadStackSize= 修改線程的默認棧大小。而這個值會影響到JVM裏面最大的線程數量,所以調整需要小心。一般情況下,在應用程序中,沒有調用本地方法時,JVM的這個參數StackShadowPages沒有必要修改。
- -XX:StackYellowPages=3 線程棧尾受保護內存區域yellow page。當方法的執行在線程棧中到達yellow page。則拋出StackOverflowError異常,注意此時jvm進程不會掛掉。
- -XX:StackRedPages=1 線程棧尾受保護內存區域red page。當方法的執行在線程棧中到達red page。進程會退出,併產生Crash日誌。(如果程序的執行期間,沒有棧空間了,並且程序的執行超過了red page地址。jvm會直接Crash掉,並不會產生Crash日誌。此時由於棧空間不足,虛擬機無法進入信號處理程序,所以無法產生Crash日誌。由此可知,控制-XX:StackYellowPages與-XX:StackRedPages兩個參數,在一定程度上有助於幫助定位問題並且有助於讓程序避免Crash,這個程度是有限的。)
鎖調優
1. 偏向鎖
- -XX:+UseBiasedLocking 開關參數。該參數開啓關閉偏向鎖。默認開啓。併發情況比較嚴重的應用中,建議關閉此選項。
- -XX:BiasedLockingStartupDelay=0 正常偏向鎖在應用啓動幾秒後纔會激活。但可設置此參數爲0,可以關閉偏向鎖啓動延遲。
2.輕量級鎖
目前未找到優化參數
3.自旋鎖
jdk1.7開始取消了自旋鎖的優化參數。虛擬機自行調整。在此不再詳細說明。
4.鎖消除
鎖消除屬於虛擬機在JIT編譯時,對運行時上下文掃描運用逃逸技術,去除不可能存在共享資源競爭的鎖的一種行爲。所以想要開啓鎖消除必須同時使用如下兩個參數。
- -XX:+DoEscapeAnalysis 開啓逃逸分析
- -XX:+EliminateLocks 開啓鎖消除
5.重量級鎖
- -XX:-UseHeavyMonitors 開關參數。默認關閉。開啓此參數可以關閉偏向鎖和輕量級鎖。
JIT優化
JIT官方參考:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
https://docs.oracle.com/javase/8/embedded/develop-apps-platforms/codecache.htm
https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE
- -Xcomp,強制虛擬機運行於編譯模式。
- -Xint,強制虛擬機運行於解釋模式。
- -Xmixed,混合模式執行。這也是Oracle Hotspot VM默認的執行模式。
- -XX:CompileThreshold,數值參數。默認10000。該參數爲方法調用計數器熱點代碼編譯閾值。Client模式下默認1500次。Server模式下默認10000次。(注意:開啓-XX:+TieredCompilation分層編譯策略。虛擬機會忽略此參數設置)。
- -XX:InterpreterProfilePercentage,解釋器監控比率,數值參數。默認33。用來計算server模式下回邊計數器熱點代碼編譯閾值。(公式:方法調用計數器閾值(CompileThreshold)× (OSR 比率(OnStackReplacePercentage)- 解釋器監控比率(InterpreterProfilePercentage)) / 100)。
- -XX:OnStackReplacePercentage,數值參數。默認140。該參數用來調整回邊計數器熱點代碼編譯閾值。(client模式下,方法調用計數器閾值(CompileThreshold)× OSR 比率(OnStackReplacePercentage)/ 100,其中 OnStackReplacePercentage 默認值爲 933,如果都取默認值,那 client 模式虛擬機的回邊計數器的閾值爲 13995。server模式下,方法調用計數器閾值(CompileThreshold)× (OSR 比率(OnStackReplacePercentage)- 解釋器監控比率(InterpreterProfilePercentage)) / 100,其中 OnStackReplacePercentage 默認值爲 140,InterpreterProfilePercentage 默認值爲 33,如果都取默認值,那 server 模式虛擬機回邊計數器的閾值爲 10700。)。
- -XX:+AggressiveOpts,開關參數。默認關閉。啓用積極的性能優化功能。開啓此選項之後,需要考慮到性能的提升,同樣也需要考慮到性能提升所帶來的不穩定風險。需要彼此權衡。
- -XX:+UseCounterDecay,開關參數。默認開啓。關閉後可以禁止JIT調用計數器熱度衰減。默認情況下,每次GC時會對調用計數器進行砍半的操作,導致有些方法一直是個溫熱,可能永遠都達不到C2編譯的1萬次的閥值。
- -XX:+TieredCompilation,開關參數。默認開啓。多層編譯策略,在某些情況下因爲有些方法C1編譯後C2不再編譯,多層編譯反而比C2編譯慢,如果發現此情況可進行禁止。(注意:開啓此參數,虛擬機將會忽略-XX:CompileThreshold參數設置的值)
當啓動分層編譯時,虛擬機將不再使用-XX:CompileThreshold指定的閾值(該參數值失效),而是使用另一套閾值系統,這套閾值系統是根據當前待編譯的方法數目動態調整的。
-------------------------------------------------------------------------------------
所謂動態調整並不複雜,在比較閾值時,Java虛擬機會將閾值與某個係數s相乘。該係數s與當前待編譯的方法數目成正相關,與編譯線程的數目成負相關。
係數s的計算方法爲:
s = queue_size_X / (TierXLoadFeedback * compiler_count_X) + 1其中 X 是執行層次,可取 3 或者 4;
queue_size_X 是執行層次爲 X 的待編譯方法的數目;
TierXLoadFeedback 是預設好的參數,其中 Tier3LoadFeedback 爲 5,Tier4LoadFeedback 爲 3;
compiler_count_X 是層次 X 的編譯線程數目。---------------------------------------------------------------------------------------
當啓動分層編譯時,即時編譯具體觸發條件如下:
當方法調用次數大於由參數 -XX:TierXInvocationThreshold 指定的閾值乘以係數,或者當方法調用次數大於由參數 -XX:TierXMINInvocationThreshold 指定的閾值乘以係數,並且方法調用次數和循環回邊次數之和大於由參數 -XX:TierXCompileThreshold 指定的閾值乘以係數時,便會觸發 X 層即時編譯。
觸發條件爲:
i > TierXInvocationThreshold * s || (i > TierXMinInvocationThreshold * s && i + b > TierXCompileThreshold * s)其中i爲方法調用計數器次數,b爲回邊調用計數器次數。
---------------------------------------------------------------------------------------
當啓動分層編譯時,觸發OSR編譯的閾值則是由參數-XX:TierXBackEdgeThreshold指定的閾值乘以係數。
---------------------------------------------------------------------------------------
如下爲JDK11.0.2 LTS版本針對JIT編譯器分層編譯策略的相關優化參數。由於資料太少。所以在此只黏貼相對默認值?
intx Tier0BackedgeNotifyFreqLog = 10 {product} {default}
intx Tier0InvokeNotifyFreqLog = 7 {product} {default}
intx Tier0ProfilingStartPercentage = 200 {product} {default}
intx Tier23InlineeNotifyFreqLog = 20 {product} {default}
intx Tier2BackEdgeThreshold = 0 {product} {default}
intx Tier2BackedgeNotifyFreqLog = 14 {product} {default}
intx Tier2CompileThreshold = 0 {product} {default}
intx Tier2InvokeNotifyFreqLog = 11 {product} {default}
intx Tier3AOTBackEdgeThreshold = 120000 {product} {default}
intx Tier3AOTCompileThreshold = 15000 {product} {default}
intx Tier3AOTInvocationThreshold = 10000 {product} {default}
intx Tier3AOTMinInvocationThreshold = 1000 {product} {default}
intx Tier3BackEdgeThreshold = 60000 {product} {default}
intx Tier3BackedgeNotifyFreqLog = 13 {product} {default}
intx Tier3CompileThreshold = 2000 {product} {default}
intx Tier3DelayOff = 2 {product} {default}
intx Tier3DelayOn = 5 {product} {default}
intx Tier3InvocationThreshold = 200 {product} {default}
intx Tier3InvokeNotifyFreqLog = 10 {product} {default}
intx Tier3LoadFeedback = 5 {product} {default}
intx Tier3MinInvocationThreshold = 100 {product} {default}
intx Tier4BackEdgeThreshold = 40000 {product} {default}
intx Tier4CompileThreshold = 15000 {product} {default}
intx Tier4InvocationThreshold = 5000 {product} {default}
intx Tier4LoadFeedback = 3 {product} {default}
intx Tier4MinInvocationThreshold = 600 {product} {default}
bool TieredCompilation = true {pd product} {default}
intx TieredCompileTaskTimeout = 50 {product} {default}
intx TieredRateUpdateMaxTime = 25 {product} {default}
intx TieredRateUpdateMinTime = 1 {product} {default}
intx TieredStopAtLevel = 4 {product} {default}
- -XX:TieredStopAtLevel=4,數值參數。默認值4。可以通過該參數限制多層編譯策略多高可以用到哪一層。(共五層0-4)。
- -XX:+UseJVMCICompiler,開關參數。默認關閉。JDK9開始支持參數。使用將JIT編譯器的C2替換爲Graal。(這是一個實驗性參數experimental,如果使用需要附加參數-XX:+UnlockExperimentalVMOptions)
- -XX:+DoEscapeAnalysis,開關參數。默認開啓。表示開啓逃逸分析,分析對象作用域不會逃逸出某個作用域。開啓此參數後JIT編譯器通過上下文掃描證明對象無法逃離方法體或當前線程,則會對對象進行進一步優化(棧上分配、同步消除/鎖消除、標量替換)。
- -XX:CICompilerCount=3,數值參數。該參數代表總編譯線程數量。client模式下默認爲1,server模式下默認爲2,如果開啓分層編譯策略-XX:+TieredCompilation那麼該參數默認值將會根據CPU邏輯核心數量擴展。如果手動設置該參數CICompilerCountPerCPU將被設置爲false。
對於四核及以上的機器,總的編譯線程的數目爲:
n = log2(N) * log2(log2(N)) * 3 / 2
其中 N 爲 CPU 核心數目。根據如上公式,四核CPU等於:2*1*3/2=3。也就是說四核CPU總編譯線程數量爲3。
- -XX:+CICompilerCountPerCPU,開關參數。默認開啓。開啓此參數編譯線程數量CICompilerCount會根據CPU數量來調整。如果手動設置-XX:CICompilerCount參數的話。本參數將被設置爲false。
- -XX:+BackgroundCompilation,開關參數。默認開啓。也就是編譯線程是後臺運行的。如果關閉此參數,一旦達到 JIT 的編譯條件,執行線程向虛擬機提交編譯請求後將會一直等待,直到編譯過程完成後再開始執行編譯器輸出的本地代碼再繼續執行方法或循環體。(也就是說如果關閉此參數,達到JIT編譯條件的方法,不會與解釋執行併發執行,必須等到JIT編譯完成以後執行編譯後的本地代碼)。
- -XX:CodeCacheMinimumFreeSpace,數值參數。設置編譯所需的最小可用空間(以字節爲單位)。當剩餘小於最小可用空間時,編譯停止。(JDK8中可以使用此參數。JDK11沒有看到此參數。)
- -XX:CompileCommand=command,method[,option],字符參數。該參數用於定製編譯需求,比如過濾某個方法不做JIT編譯,若未指定方法描述符,則對全部同名方法執行命令操作,可使用星號通配符(*)指定類或方法。該參數可多次指定,或使用 換行符(\n)分隔參數後的多個命令。
具體定製編譯需求的命令:
exclude,跳過編譯指定的方法
compileonly,只編譯指定的方法
inline/dontinline,設置是否內聯指定方法
print,打印生成的彙編代碼
break,JVM以debug模式運行時,在方法編譯開始處設置斷點
quiet,不打印在此命令後續命令
log,排除-XX:+LogCompilation除指定方法之外的所有方法的編譯日誌記錄(帶選項)。默認情況下,對所有已編譯的方法執行日誌記錄
option,此命令可用於將JIT編譯選項傳遞給指定的方法以代替最後一個參數(選項)。編譯選項在方法名稱後面的末尾設置。您可以指定多個編譯選項,以逗號或空格分隔。
其他命令:help詳細的定製編譯需求的命令用法請參照官方文檔:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html,https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE
詳細的option選項命令使用參數請參閱官方文檔:
- -XX:CompileCommandFile=filename,字符參數。設置從中讀取JIT編譯器命令的文件。默認情況下,該.hotspot_compiler文件用於存儲JIT編譯器執行的命令。命令文件中的每一行代表一個命令,一個類名和一個使用該命令的方法名。有關爲JIT編譯器指定要對方法執行的命令的更多信息,請參閱該-XX:CompileCommand選項。
- -XX:CompilerDirectivesFile=file,字符參數。該參數指定一個.json擴展名的指令文件添加到指令棧,以便於更精確的控制JIT的行爲。(此參數爲診斷參數,需要添加-XX:+UnlockDiagnosticVMOptions參數解鎖此指令)。也可以通過jcmd指令對運行時jvm的指令棧進行控制而不需要重啓jvm。
jcmd pid Compiler.directives_add file 添加指令文件到pid指定jvm指令棧棧頂。
jcmd pid Compiler.directives_remove 移除pid指定jvm指令棧棧頂指令塊,可以重複執行直到剩下默認指令塊。但無法刪除默認指令快。
jcmd pid Compiler.directives_clear 移除pid指定jvm指令棧中全部指令塊,但無法刪除默認指令塊。
jcmd pid Compiler.directives_print 打印pid指定jvm指令棧。
- -XX:CompileOnly=methods,字符參數。-請謹慎使用-。該參數限制jvm編譯的方法列表(以逗號分隔)。
例如:
-XX:CompileOnly=java/lang/String.length,java/util/List.size。
該參數雖然不支持通配符,但是可以指定類或者包,進行全部編譯。也可以指定方法名去編譯所有類中方法。
例如:
-XX:CompileOnly=java/lang/String
-XX:CompileOnly=java/lang
-XX:CompileOnly=.length
- -XX:+Inline,開關參數。默認開啓。該參數指定是否啓用方法內聯。
- -XX:InlineSmallCode,數值參數。該參數指定只有已編譯後的方法小於此字節值,纔會被JIT內聯優化。
- -XX:MaxInlineLevel,數值參數。該參數指定內聯最大嵌套數。
- -XX:MaxInlineSize,數值參數。該參數指定如果非熱點方法的字節碼大小超過該值,則無法內聯。
- -XX:MinInliningThreshold,數值參數。該參數指定如果目標方法的調用次數低於該值,則無法內聯。
- -XX:+InlineSynchronizedMethods,開關參數。默認開啓。該參數指定是否允許內聯同步方法。
- -XX:FreqInlineSize,數值參數。該參數指定如果熱點方法的字節碼大小超過該值,則無法內聯。
- -XX:LiveNodeCountInliningCutoff,數值參數。方法內聯編譯過程中IR節點數目的上限。
- -XX:MaxNodeLimit,數值參數。該參數指定單個方法編譯期間要使用的最大節點數。
- -XX:MaxTrivialSize,數值參數。該參數指定如果方法的字節碼大小少於該值,則直接內聯。
- -XX:+OptimizeStringConcat,開關參數。默認開啓。該參數指定是否啓動String串聯操作優化。
- -XX:+UseAES,開關參數。默認開啓。爲Intel,AMD和SPARC硬件啓用基於硬件的AES內在函數。Intel Westmere(2010及更新版本),AMD Bulldozer(2011及更新版本)以及SPARC(T4及更新版本)均爲支持的硬件。UseAES與UseAESIntrinsics一起使用。
- -XX:+UseAESIntrinsics,開關參數。默認開啓。默認情況下啓用UseAES和UseAESIntrinsics標誌,僅支持Java HotSpot Server VM 32位和64位。要禁用基於硬件的AES內在函數,請指定-XX:-UseAES -XX:-UseAESIntrinsics。但是如果選擇使用AES指令,也同時需要兩個參數同時使用。他們只支持jvm在server模式下運行。
- -XX:-UseCondCardMark,開關參數。默認關閉。在更新card table之前,可以檢查卡是否已經標記。默認情況下禁用此選項,並且只應在具有多個套接字的計算機上使用此選項,從而提高嚴重依賴併發操作的Java應用程序的性能。在垃圾收集器marking階段的一種優化方式,並與CMS收集器相關。但不影響G1。(由於官方把該參數放到了JIT高級優化參數裏面,具體原因還未查明,故此處也將此參數暫時放在這裏)。
- -XX:UseCodeCacheFlushing:開關參數,默認開啓。當JIT編譯的本地代碼緩存區接近或達到ReservedCodeCacheSize時,爲了編譯更多方法,JIT編譯器必須拋棄一些已經編譯的方法。如果關閉此參數。當codecache緩衝區滿時,JIT編譯器將停止工作。
- -XX:+UseSHA,開關參數。默認開啓。爲SPARC硬件啓用SHA加密散列函數的基於硬件的內在函數。UseSHA與結合使用UseSHA1Intrinsics,UseSHA256Intrinsics和UseSHA512Intrinsics選項。
- -XX:+UseSHA1Intrinsics,開關參數。默認關閉。爲SHA-1加密哈希函數啓用內在函數。
- -XX:+UseSHA256Intrinsics,開關參數。默認開啓。爲SHA-224和SHA-256加密哈希函數啓用內在函數。
- -XX:+UseSHA512Intrinsics,開關參數。默認開啓。爲SHA-384和SHA-512加密散列函數啓用內在函數。
- -XX:+UseSuperWord,開關參數。默認開啓。允許將標量操作轉換爲超級字操作。默認情況下啓用此選項。
- -XX:-UseRTMDeopt,開關參數。默認關閉。根據中止率自動調諧RTM鎖定。該比率由-XX:RTMAbortRatio選項指定。如果中止事務的數量超過中止率,則包含鎖定的方法將被取消優化並重新編譯,並將所有鎖定爲正常鎖定。默認情況下禁用此選項。-XX:+UseRTMLocking必須啓用該選項。
- -XX:-UseRTMLocking,開關參數。默認關閉。爲所有膨脹的鎖生成受限制的事務性內存(RTM)鎖定代碼,使用正常的鎖定機制作爲回退處理程序。默認情況下禁用此選項。與RTM相關的選項僅適用於支持事務同步擴展Transactional Synchronization Extension(TSX)的x86 CPU上的Java HotSpot Server VM。
RTM是英特爾TSX的一部分,它是x86指令集擴展,有助於創建多線程應用程序。RTM引入了新的指令XBEGIN,XABORT,XEND,和XTEST。該XBEGIN和XEND說明附上一組指令作爲一個事務中運行。如果在運行事務時未發現衝突,則內存和寄存器修改將在XEND指令處一起提交。該XABORT指令可用於顯式中止事務以及XEND檢查是否在事務中運行一組指令的指令。
當另一個線程嘗試訪問同一事務時,對事務的鎖定會膨脹,從而阻止最初未請求訪問該事務的線程。RTM要求在事務中止或失敗時指定後備操作集。RTM鎖是一種委託給TSX系統的鎖。
RTM提高了在關鍵區域中具有低衝突的高競爭鎖的性能(這是不能同時由多個線程訪問的代碼)。RTM還提高了粗粒度鎖定的性能,這在多線程應用程序中通常表現不佳。(粗粒度鎖定是長時間保持鎖定以最小化獲取和釋放鎖定的開銷的策略,而細粒度鎖定是通過僅在必要時鎖定並儘快解鎖來嘗試實現最大並行性的策略。此外,對於不同線程使用的輕度爭用鎖,RTM可以減少錯誤的緩存行共享,也稱爲緩存行乒乓。當來自不同處理器的多個線程訪問不同的資源時會發生 但資源共享相同的緩存行。結果,處理器重複地使其他處理器的高速緩存行無效,這迫使它們從主存儲器而不是它們的高速緩存讀取
針對於RTM(Restricted Transactional Memory)介紹最好的一片文章:JDK-8031320
- -XX:RTMAbortRatio,數值參數。RTM中止比率指定爲所有已執行RTM事務的百分比(%)。如果許多中止事務變得大於此比率,則編譯後的代碼將被去優化。-XX:+UseRTMDeopt啓用該選項時將使用此比率。此選項的默認值爲50.這意味着如果50%的所有事務都被中止,則編譯後的代碼將被去優化。
- -XX:RTMLockingCalculationDelay,數值參數。該參數指定延遲終止比率計算時間,單位毫秒。
- -XX:RTMRetryCount,數值參數。RTM鎖定代碼將在中止或忙碌時重試此選項指定的次數,然後再回退到正常鎖定機制。此選項的默認值爲5。-XX:UseRTMLocking必須啓用該選項。
- -XX:AllocateInstancePrefetchLines=1,數值參數。設置在實例分配指針之前預取的行數。默認情況下,預取的行數設置爲1。
- -XX:AllocatePrefetchDistance,數值參數。設置對象分配的預取距離的大小(以字節爲單位)。將從最後分配的對象的地址開始預取將要使用新對象的值寫入的內存。每個Java線程都有自己的分配點。負值表示基於平臺選擇預取距離。正值是預取的字節數。
- -XX:AllocatePrefetchInstr,數值參數。將預取指令設置爲在分配指針之前預取。只有Java熱點服務器VM支持此選項。可能的值從0到3。值後面的實際指令取決於平臺。默認情況下爲0。
- -XX:AllocatePrefetchLines,數值參數。使用編譯代碼中生成的預取指令設置在最後一次對象分配後要加載的高速緩存行數。如果最後分配的對象是實例,則默認值爲1;如果是數組,則默認值爲3。
- -XX:AllocatePrefetchStepSize,數值參數。設置順序預取指令的步長。
- -XX:AllocatePrefetchStyle,數值參數。爲預取指令設置生成代碼樣式。的風格參數是從0至3的整數。(0,不生成預取指令。1,每次分配後執行預取指令,默認值。2,使用TLAB水印指針來確定何時執行預取指令。3,在SPARC上使用BIS指令進行分配預取。)
調試與問題排查
jvm調試控制參數
- -XX:MaxJavaStackTraceDepth=1024 當發生StackOverflowError異常的時候,打印到日誌裏面的棧信息有一定可能性不是全的。必要的時候可以調整此值來保障棧信息的完整性。可以以將此值設置爲-1,此時將會打印全部堆棧。
- -XX:-PrintCompilation,開關參數。默認關閉。開啓此參數可以打印應用中JIT編譯情況。打印內容:第一列是時間。第二列是Java虛擬機維護的編譯ID。接下來是標識:%(是否OSR編譯),s(是否synchronized方法),!(是否包含異常處理器),b(是否租塞了應用線程,顯示了b則代表編譯線程不是後臺運行,可瞭解一下參數-Xbatch),n(是否爲native方法)。再接下來則是編譯層次,以及方法名。如果是OSR編譯,那麼方法名後面還會跟着@以及循環所在的字節碼。
- -XX:-PrintGC,開關參數。默認關閉。爲應用打印GC日誌。
- -XX:-PrintGCDetails,開關參數。默認關閉。爲應用打印GC詳細日誌。
- -Xloggc,字符參數,指定gc日誌輸出位置。例如:-Xloggc:/home/rock/gc_%t.log
- -Xlog,jdk11參數。該參數可以支持多種日誌打印,例如gc日誌(-Xlog:gc*:file=/home/rock/gc_%t_%p.log::filecount=5,filesize=20480),由於內容比較多。更詳細的描述請參照官方文檔https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-BE93ABDC-999C-4CB5-A88B-1994AAAC74D5
- -XX:-LogCompilation,開關參數。默認關閉。開啓此參數會記錄JIT編譯活動到當前工作目錄中名爲hotspot.log的文件中,也可以使用-XX:LogFile參數指定輸出文件路徑。(該參數爲診斷參數,需要-XX:+UnlockDiagnosticVMOptions參數來打開此隱藏參數)。您也可以使用-XX:+PrintCompilation參數,將冗長的JIT編譯信息打印到控制檯。
- -XX:-PrintInlining,開關參數。默認關閉。打印方法獲得內聯的信息到控制檯。
- -XX:LogFile,字符參數。指定日誌數據的寫入文件的絕對路徑。如果不指定,默認情況下,會在當前工作目錄創建hotspot.log文件。
- -XX:-PrintAssembly,開關參數。默認關閉。打印裝配字節碼的native方法,需要外部鏈接庫disassembler.so。
- -XX:-HeapDumpOnOutOfMemoryError,開關參數。默認關閉。啓動該參數後發生OOM時自動導出heap信息。
- -XX:-HeapDumpAfterFullGC,開關參數。默認關閉。啓動該參數後發生FullGC後自動導出heap信息。
- -XX:-HeapDumpBeforeFullGC,開關參數。默認關閉。啓動該參數後發生FullGC前自動導出heap信息。
- -XX:HeapDumpPath,字符參數。該參數指定heapdump文件輸出位置。
- -XX:-VerifyBeforeGC,開關參數。默認關閉。該參數是在GC前進行校驗的參數,在校驗過程中當發生地址異常的化會打印出異常的地址,並且讓JVM crash,因爲這個參數每一次GC都要檢查,包括新生代的GC,影響一定的性能,並不適合在產品環境中使用,但對發現GC中的對象問題,卻非常有幫助。
- -XX:ErrorFile,字符參數。該參數指定jvm crash時候的錯誤信息輸出位置。例如:-XX:ErrorFile=/path/hs_error%p.log,其中“%p”用來獲取當前進程ID。
- -XX:OnError,字符參數。該參數指定jvm crash時候執行命令行。每個命令以“;”分隔。
- -XX:-ShowMessageBoxOnError,開關參數。默認關閉。當jvm crash的時候在linux裏會啓動gdb 去分析和調式,適合在測試環境中使用。
- -XX:NativeMemoryTracking=[off | summary | detail],字符選項參數。默認off。該參數用來開啓NMT功能,用來排查堆外內存使用情況。虛擬機啓動後可以使用“jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]”命令查看。(開啓NMT會有5%-10%的性能耗損)
- -XX:-PrintNMTStatistics,開關參數。默認關閉。該參數指定在開啓了NativeMemoryTracking參數後,在jvm退出時控制檯打印NMT信息。
- -XX:G1SummarizeRSetStatsPeriod,數值參數。該參數指定了G1的RSet的統計週期,也就是經過多少次GC後進行一次統計。在JDK11下使用日誌gc+remset=trace選項配合該參數,可以查看是否發生過RSet壓縮coarsening。(該參數爲診斷參數,需要-XX:+UnlockDiagnosticVMOptions參數來打開此隱藏參數)
幾種OOM介紹
- java.lang.OutOfMemoryError: Java heap space:
這種是java堆內存不夠,一個原因是真不夠,另一個原因是程序中有對象創建沒有銷燬也就是錯誤的代碼書寫邏輯;
如果是java堆內存不夠的話,可以通過調整JVM下面的配置來解決:
< jvm-arg>-Xms3062m < / jvm-arg>
< jvm-arg>-Xmx3062m < / jvm-arg>
- java.lang.OutOfMemoryError: GC overhead limit exceeded
【解釋】:JDK6新增錯誤類型,當GC爲釋放很小空間佔用大量時間時拋出;一般是因爲堆太小,導致異常的原因,沒有足夠的內存。
【解決方案】:
1、查看系統是否有使用大內存的代碼或死循環;
2、通過添加JVM配置,來限制使用內存:
< jvm-arg>-XX:-UseGCOverheadLimit< /jvm-arg>
- java.lang.OutOfMemoryError: Metaspace:
這種是P區內存不夠,可通過調整JVM的配置:
< jvm-arg>-XX:MaxMetaspaceSize=128m< /jvm-arg>
- java.lang.OutOfMemoryError: Direct buffer memory:
調整-XX:MaxDirectMemorySize= 參數:
< jvm-arg>-XX:MaxDirectMemorySize=128m< /jvm-arg>
- java.lang.OutOfMemoryError: unable to create new native thread
【原因】:Stack空間不足以創建額外的線程,要麼是創建的線程過多,要麼是Stack空間確實小了。
【解決】:
1.通過 -Xss啓動參數減少單個線程棧大小,這樣便能開更多線程(當然不能太小,太小會出現StackOverflowError);
2.通過-Xms -Xmx 兩參數減少Heap大小,將內存讓給Stack(前提是保證Heap空間夠用)。
- java.lang.StackOverflowError
【原因】:這也內存溢出錯誤的一種,即線程棧的溢出,要麼是方法調用層次過多(比如存在無限遞歸調用),要麼是線程棧太小。
【解決】:優化程序設計,減少方法調用層次;調整-Xss參數增加線程棧大小。
jvm crash & heap dump & core dump & High CPU
模擬High CPU----分析
這個模擬很容易,簡單的無限循環就可以做到。
使用top命令查找high cpu的進程:
使用top -p 1997 -H命令查找high cpu的線程:
轉換2044爲十六進制7fc。使用jstack -l 1977>jstack.1977.log命令導出thread dump。然後再文檔中找到本地線程id爲7fc的線程。可以很明顯的看到當前線程執行的方法和正在做的high cpu的事情。
core dump分析
- gdb分析:
- jstack分析:(注意下面圖片中的命令,在jdk11環境下需要藉助jdk8的java命令纔可以直接分析core文件。如果直接使用jdk11的java命令會提示:sun.jvm.hotspot.debugger.DebuggerException: cannot open binary file。)
- jmap分析:(注意下面圖片中的命令,在jdk11環境下需要藉助jdk8的java命令纔可以直接分析core文件。如果直接使用jdk11的java命令會提示:sun.jvm.hotspot.debugger.DebuggerException: cannot open binary file。)
模擬jvm crash使用CrashAnalysis幫助分析crash 文件,簡單例子:
想要模擬jvm crash並不容易,因爲普通的java異常都已經被程序員、應用層、虛擬機攔截到了。所以想要模擬jvm crash必須要C/C++代碼的配合。咱們先看代碼:
helloworld.h文件:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_com_test_rock_oom_TestCrash1
#define _Included_com_test_rock_oom_TestCrash1
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: displayHelloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_test_rock_oom_TestCrash1_displayHelloWorld
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
helloworld.cpp文件:
#include "HelloWorld.h"
#include <stdio.h>
#include <jni.h>
/*
* Class: HelloWorld
* Method: displayHelloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_test_rock_oom_TestCrash1_displayHelloWorld
(JNIEnv *, jobject)
{
int* p = NULL;
*p = 3;
//非法地址操作異常
printf("Hello World!\n");
return;
}
TestCrash1.java文件:
package com.test.rock.oom;
/**
* 測試jvm crash
* 啓動時增加如下參數,指定crash日誌打印位置
* -XX:ErrorFile="C:\workspaces\cresh_%p.log"
*/
public class TestCrash1 {
public native void displayHelloWorld();// java native方法申明
static {
System.load("C:/qt_workspace/build-helloworld-Desktop_Qt_5_12_2_MinGW_64_bit-Release/release/helloworld.dll");
}
public static void main(String[] args) {
Thread t = new Thread(()->{
TestCrash1 helloWorld = new TestCrash1();
helloWorld.displayHelloWorld();
});
t.start();
}
}
下面我們來看CrashAnalysis的分析結論:
這是首頁,大概分析了問題的可能性:
這個頁面指定了出現問題的dll文件:
這個頁面指定了出現問題的線程以及堆棧信息:
通過分析crash的log文件我們還是可以進一步的查詢到更詳細的信息的:
這裏指定了有關crash的java代碼部分可以很明顯的定位:
crash問題總結:crash有很大一部分原因是C/C++語言部分出現了問題導致的異常崩潰。換句話說在運行時角度上來講大部分原因是機器碼部分出現了問題導致了crash問題(我們都知道jvm運行分爲java字節碼解釋執行和JIT編譯器編譯後的機器碼的直接執行)。所以說JIT編譯器編譯後的機器碼也有可能會導致此類異常,如果由於JIT編譯器導致了crash,那就需要詳細排查目標java代碼的編寫所涉及到的上下文信息了。也可以使用臨時解決方案,JIT優化參數:-XX:CompileCommand的exclude指令,臨時排除掉優化方法即可。
另附上linux下編譯過程:
g++ helloworld.cpp -c
gcc -shared helloworld.o -o libHelloworld.so
模擬OOM使用eclipse MAT分析heap dump文件,簡單例子:
測試代碼:
package com.test.rock.oom;
import java.util.ArrayList;
/**
* 測試oom
* 啓動時增加如下參數使程序發生oom的時候可以打印堆棧信息到磁盤
* -XX:+HeapDumpOnOutOfMemoryError
*/
public class TestCrash {
private TestCrash t1 = null;
public static void main(String[] args){
ArrayList<StubClass> list = new ArrayList<StubClass>();
int count = 1000000000;
for (int i = 0; i < count; i++) {
StubClass bj = new StubClass();
list.add(bj);
}
}
static class StubClass {
private Integer n = new Integer(123334);
private Integer m = new Integer(123334);
public void print() {
System.out.println("m="+m);
System.out.println("n="+n);
}
}
}
點擊默認報告頁面的See stacktrace。
從這個頁面可以看出,出現了OOM異常。目標類是我們的測試類TestCrash.class。
在Overview頁面點擊Dominator Tree。
看到佔用內存比例比較大的一項,繼續點擊向下查找。
這裏可以看到,很明顯是我們的測試類中的內部類StubClass.class創建過多導致了OOM異常的發生。
jdk命令行調試工具
1)jmap -histo:live <pid>
命令可以手動啓動Full GC
2) jps [options] [hostid]
jps主要用來輸出JVM中運行的進程狀態信息。
-q 輸出類名、Jar名和傳入main方法的參數
-m 輸出傳入main方法的參數
-l 輸出main類或Jar的全限名
-v 輸出傳入JVM的參數
如下查看運行的java進程信息,打印jar名以及運行main方法傳入的參數:
3) jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]
jstat命令可以用於持續觀察虛擬機內存中各個分區的使用率以及GC的統計數據。vmid是Java虛擬機ID,在Linux/Unix系統取進程ID。
如下面輸出的信息,採樣時間間隔爲1000ms,採樣5次:
上述各個列的含義:
- S0C、S1C、S0U、S1U:young代的Survivor 0/1區容量(Capacity)和使用量(Used)。0是FromSurvivor,1是ToSurvivor。
- EC、EU:Eden區容量和使用量
- OC、OU:年老代容量和使用量
- MC、MU:元數據區(Metaspace)已經committed的內存空間和使用量
- CCSC、CCSU:壓縮Class(Compressed class space)committed的內存空間和使用量。
- YGC、YGT:young代GC次數和GC耗時
- FGC、FGCT:Full GC次數和Full GC耗時
- CGC 和 CGCT,它們分別代表併發 GC STW 的次數和時間
- GCT:GC總耗時
可以通過分區佔用量上看到,在第2-3秒之間發生了一次YGC。YGC次數+1,並且Survivor from區的內存空間從1233.7->0,Survivor from從0->1536。Eden區也釋放了很多內存空間。其他變化的空間佔用也有元數據區以及元數據區的壓縮Class區。Compressed class space也是元數據區的一部分,默認是1G,也可以關閉。具體的jvm8內存分佈不再詳述。下一篇GC優化會再展開整理下。
如果只看gc的總統計信息,也可以用jstat -gcutil vmid查詢:
4) jmap [option] pid
- jhsdb jmap --heap --pid <pid>或者jmap -heap <pid>查看heap情況
可以用來查看堆內存的使用詳情。
- jmap -dump:format=b,file=jmap.hprof <pid>
生成內存快照文件,內存快照文件可以使用eclipse MAT工具分析。
5)jstack [option] pid
jstack可以用來查看Java進程內的線程堆棧信息。
-l long listings,會打印出額外的鎖信息,在發生死鎖時可以用jstack -l pid來觀察鎖持有情況
注意:當進程CPU使用率過高而導致無法打印線程堆棧信息時,可以使用-F參數強行打印。
例如:jstack -F 23040或者jhsdb jstack --pid 23040
調試時的坑
- 執行jmap異常 operation not permitted
vim /etc/sysctl.d/10-ptrace.conf
kernel.yama.ptrace_scope = 0
- coredump
ulimit -c unlimited
linux命令行調試工具
top
排查問題常用按鍵:
e:切換列表部分數值單位(k,m,g,t,p)
E:切換統計部分數值單位(k,m,g,t,p,e)
M:按照內存佔用率排序列表
P:按照CPU佔用率排序列表
常用列說明:
PID 進程ID
USER 進程所有者用戶名
PR 優先級
NI nice值,負值表示高優先級
VIRT 進程使用的虛擬內存總量,VIRT=SWAP+RES
RES 進程使用的、未被換出的物理內存大小,RES=CODE+DATA
SHR 共享內存大小
S 進程狀態,(D=不可中斷的睡眠狀態,R=運行,S=睡眠,T=跟蹤/停止,Z=殭屍進程)
%CPU 上次更新到現在的CPU時間佔用百分比
%MEM 進程使用的物理內存百分比
TIME+ 進程使用的CPU時間總計
COMMAND命令名/命令行
SWAP 進程使用的虛擬內存中,被換出的大小
CODE 可執行代碼佔用的物理內存大小
DATA 可執行代碼以外的部分(數據段+棧)佔用的物理內存大小
pmap <pid>
第一列,內存塊起始地址
第二列,佔用內存大小
第三列,內存權限
第四列,內存名稱,anon表示動態分配的內存,stack表示棧內存
最後一行,佔用內存總大小,請注意,此處爲虛擬內存大小,佔用的物理內存大小可以通過top查看
BTrace監控調試
一張圖看懂BTrace工作原理
描述
BTrace官網:https://github.com/btraceio/btrace
BTrace是一個可以在不停止目標jvm進程,不修改java源代碼的基礎上對生產環境服務器上的jvm進程進行監控和測試。他使用ASM,instrument,JVMTI,Java Compiler Api等技術直接修改正在運行中java字節碼信息,可以監控所有java方法的入參和出參。可以大大減少定位問題和問題排查的時間。雖然功能強大,但也有弊端,無法遠程調試。但是對於排查問題,獲取生產環境的關鍵數據、復現和定位問題來講。這些弊端已經算得上非常小了。因爲如果問題不能夠在開發環境或測試環境復現且必須要在生產環境獲取必要數據,但是數據庫以及程序員寫的log又不能滿足要求的時候,BTrace工具就顯得格外的搶眼了(畢竟出了問題,就意味着下次重啓jvm進程的時間已經很近了,而且現在哪一家公司的生產環境怎麼會負載一臺機器運行呢?)。對於生產環境大部分的問題,只有運行中的程序數據纔可以代表一切,而在開發人員手中靜止的代碼是沒有辦法解決所有問題的。
BTrace的功能強大請參見官網提供的樣例代碼:https://github.com/btraceio/btrace/tree/master/samples
支持jdk11
BTrace官方的版本只支持到jdk8。本人親測在jdk1.8.0_201版本下可以運行官方版本。爲了能夠在後續版本中使用。小編修改了BTrace源代碼和ASM源代碼,才保證在jdk11下通暢運行。主要問題在於jdk9以後出現的模塊化技術與越來越嚴格的jdk內部類的使用權限、還有在於官方的btrace-asm-5.0.4.jar是基於asm-all-5.2.jar,所以asm版本有點低,支持的jdk版本太低了。當然了也沒這麼簡單,小編在這裏不再詳述了。小編在這裏提供了支持jdk11版本的資源供大家下載,linux系統覆蓋btrace、btracec文件和三個jar,win系統覆蓋btrace.bat、btracec.bat文件和三個jar即可使用。小編親測在win10 x64專業版、Ubuntu18.04.2LTS兩個系統,jdk1.8.0_201和jdk11.0.2 LTS兩個版本。均已跑通所有官方提供的BtraceScript樣例代碼(排除DTrace相關)。
jdk11小測試
測試java代碼
public class TestBt {
public static void main(String[] args){
new Thread(()->{
TestBt tb = new TestBt();
int icount = 0;
while(true){
try{
Thread.sleep(1000 * 10);
tb.thread_process(icount++);
}catch(Exception e){
e.printStackTrace();
}
}
}).start();
}
public String thread_process(int icount){
System.out.println(icount);
return "漢字:" + icount;
}
}
Btrace測試腳本
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.println;
@BTrace
public class BtraceScript {
@OnMethod(
clazz = "TestBt",
method = "thread_process",
location = @Location(Kind.RETURN)
)
public static void thread_process(int icount, @Return String result) {
println("icount: " + icount);
println(result);
}
}
windows測試--目標進程
windows測試--監控進程
linux測試--目標進程
linux測試--監控進程
webBTraceUtil小工具
一張圖看懂小工具工作原理
小工具頁面
使用須知:
- connect按鈕用來啓動socket鏈接
- Clean Up和Clean Up All兩個按鈕用來停止並清除BTrace測試腳本的任務。這點很重要。這就好比使用BTrace官方版命令行Ctrl+C,然後選擇1。是一樣的效果。目的是用來把已經在目標jvm中運行Class字節碼再次修改回來。我們都知道BTrace是通過各種技術修改正在運行中java字節碼信息達到目的的。我們如果沒有在退出的時候修改回來,這個被修改的Class信息會永遠停留在目標jvm中。即便如此官方BTrace也添加了有效的ShutdownHook與Signal(SIGINT)。所以網上其他文章或教程等所說的BTrace修改的Class字節碼信息會永遠停留在目標jvm進程中的這個觀點是錯誤的。小編在這裏爲官方BTrace“平反”呵呵。我們再說回這兩個按鈕,Clean Up的作用是停止並清除當前頁面發起的BTrace測試腳本任務,Clean Up All的作用是停止並清除所有頁面發起的BTrace測試腳本任務。(如果大家不小心關閉頁面,或者刷新頁面的話。不用擔心。小工具會自動停止並清除當前頁面發起的BTrace測試腳本任務。或者大家可以不用點擊這兩個按鈕,直接關閉頁面即可,也可以達到Clean Up的效果。)
對於小編爲官方BTrace“平反”的證據如下:主要涉及到BTrace官方源代碼com.sun.btrace.agent.Client類中:retransformLoaded()與OnExit()兩個方法。另外一個重要的類com.sun.btrace.runtime.BTraceTransformer類的transform()方法。在一個BTrace腳本的生命週期中,會執行兩次retransformLoaded()方法,第一次的目的是爲了修改目標Class的字節碼是爲了測試數據。第二次執行目的是爲了修正回原來類的字節碼。
Client.OnExit()方法中cleanupTransformers()方法的執行清除了目標類過濾器。BTraceTransformer.transform()方法,會在if(filter.matchClass******這一行返回NULL。
引用Oracle官方:If the implementing method determines that no transformations are needed, it should return
null
. Otherwise, it should create a newbyte[]
array, copy the inputclassfileBuffer
into it, along with all desired transformations, and return the new array. The inputclassfileBuffer
must not be modified.詳見:https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/ClassFileTransformer.html
小編在這裏不再多說了。請參考BTrace官方源代碼,或Oracle官方對於ClassFileTransformer.transform()方法的描述。再或者開啓BTrace的debug模式查看詳細的日誌輸出。再~再~再~在不行,請自行編寫instrumentation相關技術代碼自己測試或者參見小編寫的測試代碼:https://pan.baidu.com/s/1vRT8lGSoi1bsDNWNuo-UCQ 提取碼 r7n2。
- 小編在這裏再剔除網上的另一個謠言。BTrace官方版本本來就支持遠程調試,對於這一點不需要修改任何源代碼。(即便如此,小編在此也不建議大家在生產環境下進行遠程調試)
java -javaagent:"C:\電腦搬家資料拷貝-重要\類庫\btrace\btrace1\build\btrace-agent.jar"=port=2020,statsd=,debug=true,bootClassPath=.,probeDescPath=. --add exports=java.base/jdk.internal.misc=ALL-UNNAMED TestBt
這裏請注意:雖然遠程調試的時候PID沒有任何意義。但是如果沒有會報錯。
- debug開關按鈕,功能等同於命令行-v的選項開關。小編自認爲debug功能比較有用所以顯示的添加了一個按鈕。開啓此功能,debug信息會打印到目標JVM進程控制檯與webBTraceUtil小工具控制檯。
- 當BTrace腳本中有OnEvent註釋的時候,可以使用event按鈕發送指定事件到agent端。窗口中輸入字符,等同於官方命令行Ctrl+C選擇3的情況。窗口中不輸入字符,等同於官方命令行Ctrl+C選擇2的情況。
- BTrace腳本請輸入在最上面的textarea中。
- BTrace命令行命令,請輸入在BTrace command line輸入框中。
1. 輸入規則是剔除原命令行中btrace字符和腳本文件的輸入即可,其他規則和命令行的一樣。
例如:
命令行:btrace 8952 BtraceScript.java 小工具:8952
命令行:btrace --version 小工具:--version
命令行:btrace -v 8952 BtraceScript.java 小工具:-v 89522. 小工具剔除了-I選項。此選項在小工具中不起作用。
3. webBTraceUtil的BTrace command line命令行輸入框中,沒有官方原命令行中的引號那麼一說,可以直接省略。這點請注意。
例如:這裏的classpath路徑不需要引號
- GO按鈕的作用是發送BTrace腳本以及BTrace命令到小工具後臺並啓動測試任務。如果重複點擊GO按鈕,小工具會自動停止並清除掉上一個本頁面發起的測試腳本任務,並執行新的測試任務。
- 當前頁面發起的測試腳本任務返回信息會顯示在最先面的textarea中。
- 本小工具支持多人同時測試,互不影響。但也要注意,請給生產環境留下喘氣的時間。
- 小工具不支持Dtrace測試。請使用官方版本或小編提供的支持jdk11版本。
- 使用webBTraceUtil小工具,不需要安裝BTrace官方版本,也不需要配置任何BTRACEHOME環境變量。除了以下注意事項以外直接拷貝直接使用。
注意:
jdk1.8版本請拷貝JAVA_HOME/lib目錄下的tools.jar。到webBTraceUtil/lib目錄下。注意這裏一定是測試目標機器的tools.jar。win版本jdk的tools.jar無法在linux版本jdk下使用。
jdk11版本,不需要拷貝tools.jar因爲沒有。但是所測試的目標JVM進程,啓動參數必須添加--add-exports java.base/jdk.internal.misc=ALL-UNNAMED否則無法運行。
小編提供了兩個版本web小工具,一個支持jdk1.8一個支持jdk11。
資源:webBTraceUtil_for_JDK11 webBTraceUtil_for_JDK1.8
BTrace總結:
- 本文所有內容,以及小編提供的支持jdk11版本的BTrace,和兩個BTrace的web小工具。都是基於官方BTrace1.3.11.3版本的源代碼。
- 想要正確從目標jvm進程獲取漢字不亂碼。就必須在目標JVM所運行的class字節碼編譯的時候使用utf-8格式編譯。而BTrace腳本文件也必須使用utf-8編碼格式。javac -encoding utf-8 TestBt.java
- 在JDK11下運行程序,目標jvm進程啓動時需要增加--add-exports java.base/jdk.internal.misc=ALL-UNNAMED參數。因爲agent.jar使用了一些jdk.internal.misc包下的類。
- 由於BTrace的agaent端和client端使用Socket技術通信。所以建議不要進行遠程監控調試。(建議直接使用官方的本地BTrace腳本調試方式或者小編提供的webBTraceUtil小工具)
- BTrace默認會佔用服務器2020端口。請注意服務器端口或修改。
- 請使用對應版本,在jdk1.8下編譯出來的BTrace無法在jdk11運行並attach到jdk11的JVM進程。
- 有任何問題,請加小編微信:longyunjiangliang。小編也希望能夠與您有更多的技術交流。
可視化的GC日誌分析
可很直觀的查看到GC調優重要性能指標----吞吐量、STW時間。
GCEasy
GCViewer
圖形界面性能監控工具
開啓遠程監控-JMX
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
開啓遠程監控-jstatd
- JDK11
grant codebase "jrt:/jdk.jstatd" {
permission java.security.AllPermission;
};
grant codebase "jrt:/jdk.internal.jvmstat" {
permission java.security.AllPermission;
};
- JDK8
grant codebase "file:${java.home}/../lib/tools.jar" {
permission java.security.AllPermission;
};
啓動RMI服務:jstatd -J-Djava.rmi.server.hostname=192.168.1.8 -J-Djava.security.policy=./jstatd.jdk11.policy -p 1100
VisualVM
- File->Load->Thread Dump(對應的是jstack生成的線程棧文件)
- File->Load->Heap Dump(對應的是jmap生成的堆棧文件)
JDK Mission Control 7.1.0
最新JMS7.1.0編譯並運行:
第一:下載安裝版本控制mercurial
Mac OS X:brew install mercurial
Windows : https://www.mercurial-scm.org/wiki/Download
linux :sudo apt-get install mercurial
第二:下載JMC源代碼
hg clone http://hg.openjdk.java.net/jmc/jmc/
第三:安裝Maven
https://maven.apache.org/install.html
第四:編譯(此處需要jdk8或後續版本,小編使用jdk11.0.2編譯通過)。此過程首次分爲三個小步驟,後續編譯只需要最後一步
打開一個命令行窗口:
- cd releng/third-party
mvn p2:site
mvn jetty:run開啓一個新的命令行窗口:
- cd core
mvn install- mvn clean package
第五:運行
Windows:target\products\org.openjdk.jmc\win32\win32\x86_64\jmc.exe -vm %JAVA_HOME%\bin
Mac OS X:target/products/org.openjdk.jmc/macosx/cocoa/x86_64/JDK\ Mission\ Control.app/Contents/MacOS/jmc -vm $JAVA_HOME/bin
JDK 11:
-XX:StartFlightRecording
JDK 9,JDK 10:
-XX:+UnlockCommercialFeatures -XX:StartFlightRecording
JDK 8u40,
-XX:+UnlockCommercialFeatures -XX:StartFlightRecording=name=whatever
JDK 7u4,JDK 8u20:
-XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:StartFlightRecording=name=whatever
如果要避免全部附加到進程,可以指定一個文件名,即-XX:StartFlightRecording=filename=rec.jfr,然後通過設置持續時間(即工期=60)轉儲記錄,或者在JVM退出:
-XX:StartFlightRecording= filename=rec.jfr,dumponexit=true
可以在JMC中打開然後打開記錄文件,在JDK 11中,這已進一步簡化,因此只需指定:
-XX:StartFlightRecording:filename=c:\recs
並且文件名將在指定的目錄中生成,並在JVM退出時自動轉儲
內容來自:https://cloud.tencent.com/developer/ask/151108/answer/262468
JProfile
https://download.csdn.net/download/jl19861101/11078717
總結
JVM性能調優思路
(本文只從理論而不是實際效果的角度,客觀的評論方式方法的存在)
1.內存調優
2.GC調優
3.多線程調優
4.JIT調優
JVM生產環境問題排查思路
(本文只從JVM層面闡述關注點,不包括程序員在代碼層面以及開發階段的問題排查方式方法)
參考資料:
https://docs.oracle.com/javase/9/migrate/toc.htm#JSMIG-GUID-9E847B7E-1F6B-4AD4-A5EE-66F8EF8BA9F6
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc.html
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html
https://docs.oracle.com/javase/8/embedded/develop-apps-platforms/codecache.htm
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
https://www.oracle.com/technetwork/cn/articles/java/g1gc-1984535-zhs.html
http://openjdk.java.net/jeps/197
https://blog.csdn.net/qq_22194659/article/details/85245144
https://blog.csdn.net/jl19861101/article/details/88566316
https://blog.csdn.net/foolishandstupid/article/details/77596050
https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html
https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#large_pages
《深入理解Java虛擬機_JVM高級特性與最佳實踐 第2版》
《實戰Java虛擬機:JVM故障診斷與性能優化》
《Java性能優化權威指南》