讀懂 GC 日志
這個課題拖了很久了,可以說讀懂 GC 日志對於一個 java 后端開發來說是必須的事情。然而讀懂 GC 日志並非是一件容易的事情,首先要對 JVM 內存模型有基本的理解,熟悉常用的 JVM 垃圾回收器,其次要知曉每個參數能夠產生的效果,再次要清楚不同的垃圾回收器的日志該如何去讀。這些要求給 java 后端開發讀懂 gc 日志帶來了很大的挑戰,包括很多 3-5 年的開發(基本是個人思想惰性造成的,不求甚解,自己差點就步入這個行列了。。。),然而這個課題卻是一個合格開發繞不去的坎,那么現在就開始學習一下 GC 日志的解讀方式。
安全點
在設置 JVM 參數時,本着多多益善的原則,我們可能會加上如下的配置
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCApplicationConcurrentTime
-XX:+PrintGCDetails
這樣會在 gc 日志中產生大量的內容,例如:
2018-08-21T13:40:39.636+0800: 328.169: Application time: 0.0000533 seconds
2018-08-21T13:40:39.636+0800: 328.169: Total time for which application threads were stopped: 0.0001573 seconds, Stopping threads took: 0.0000241 seconds
2018-08-21T13:40:39.636+0800: 328.169: Application time: 0.0000910 seconds
2018-08-21T13:40:39.636+0800: 328.170: Total time for which application threads were stopped: 0.0001603 seconds, Stopping threads took: 0.0000223 seconds
這些內容的上下也許看不到任何和 GC 相關的日志,那么這些日志是什么呢?
從參數名字上來看,會覺得上述參數是和 GC 相關的,其實不然。這里前兩個參數的打開實際上是負責記錄所有的安全點,而不只是 GC 暫停;第三個參數確和 GC 有關。如果非要和 GC 扯上關系的話,那么 GC 日志前會有Appliction time: xxx seconds
,而后面會有Total time for which ...
,也就是說,這兩條語句把 GC 打的日志包裹了起來,這些有助於幫助分析 GC 日志,例如下面:
2018-08-21T13:40:43.501+0800: 332.034: Application time: 0.5838027 seconds
{Heap before GC invocations=3 (full 1):
par new generation total 2184576K, used 1795001K [0x0000000680000000, 0x0000000720000000, 0x0000000720000000)
eden space 1747712K, 100% used [0x0000000680000000, 0x00000006eaac0000, 0x00000006eaac0000)
from space 436864K, 10% used [0x0000000705560000, 0x000000070838e5b0, 0x0000000720000000)
to space 436864K, 0% used [0x00000006eaac0000, 0x00000006eaac0000, 0x0000000705560000)
concurrent mark-sweep generation total 2621440K, used 165820K [0x0000000720000000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 67048K, capacity 68642K, committed 68736K, reserved 1110016K
class space used 8241K, capacity 8546K, committed 8576K, reserved 1048576K
2018-08-21T13:40:43.502+0800: 332.036: [GC (Allocation Failure) 332.036: [ParNew
Desired survivor size 223674368 bytes, new threshold 15 (max 15)
- age 1: 63634632 bytes, 63634632 total
- age 2: 1064928 bytes, 64699560 total
- age 3: 24489776 bytes, 89189336 total
: 1795001K->92332K(2184576K), 0.1389036 secs] 1960821K->258153K(4806016K), 0.1390975 secs] [Times: user=0.37 sys=0.16, real=0.14 secs]
Heap after GC invocations=4 (full 1):
par new generation total 2184576K, used 92332K [0x0000000680000000, 0x0000000720000000, 0x0000000720000000)
eden space 1747712K, 0% used [0x0000000680000000, 0x0000000680000000, 0x00000006eaac0000)
from space 436864K, 21% used [0x00000006eaac0000, 0x00000006f04eb3f0, 0x0000000705560000)
to space 436864K, 0% used [0x0000000705560000, 0x0000000705560000, 0x0000000720000000)
concurrent mark-sweep generation total 2621440K, used 165820K [0x0000000720000000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 67048K, capacity 68642K, committed 68736K, reserved 1110016K
class space used 8241K, capacity 8546K, committed 8576K, reserved 1048576K
}
2018-08-21T13:40:43.642+0800: 332.175: Total time for which application threads were stopped: 0.1406530 seconds, Stopping threads took: 0.0008240 seconds
從上段日志可以得知,應用程序在前 0.5838027秒是在處理實際工作的,然后所有應用線程暫停了 0.1406530秒,其中等待所有應用線程到達安全點用了 0.0008240秒。而暫停這 0.1406530秒,實際上用在了GC上,可以看到 GC 花費的時間 real=0.14 secs
,和應用線程暫停的時間相對應。這樣看來,似乎這些日志用處不是很大。然而,作為這一小節的主角,它還是有一些用處的,那就是分析安全點。
其實,程序進入安全點不只是在 GC 的時候,不同的 JIT 活動,偏向鎖擦除,特定的 JVMTI 操作,這些都會導致程序暫停進入安全點。所以會發現這些安全點的日志特別多,而打印安全點日志就是為了發現觸發安全點是否存在異常和優化的空間,盡管可能只花費了幾十毫秒,但是如今大量並發的時代,這幾十毫秒意味着很大的性能浪費與不友好。
加上如下這組 JVM 參數:
-XX:+PrintSafepointStatistics
-XX:+PrintSafepointStatisticsCount=1
該配置會將額外的信息輸出到日志中,類似下面這樣:
5.141: RevokeBias [ 13 0 2 ] [ 0 0 0 0 0 ] 0
Total time for which application threads were stopped: 0.0000782 seconds, Stopping threads took: 0.0000269 seconds
這里可以看到,多了上面一行日志,那分別都表示什么呢?
- JVM 啟動后所經歷的毫秒數(5.141)
- 觸發這次 STW 的操作名是
RevokeBias
,如果看到是no vm operation
,就說明這是一個“保證安全點”。JVM 默認會每秒觸發一次安全點來處理那些非緊急的排隊操作。 - 停在安全點的線程數量(13)
- 在安全點開始時仍在運行的線程數量(0)
- 虛擬機操作開始執行前仍處於阻塞狀態的線程數量(2)
- 到達安全點時各個階段以及執行操作所花的時間(0)
-XX:GuaranteedSafepointInterval=0 可以關閉第二點提到的保證安全點;-XX:GuaranteedSafepointInterval=1000 則表示 1秒觸發一次
第二個括號里很多個 0,網上找了一段解釋:This part is the most interesting. It tells us how long (in milliseconds) the VM spun waiting for threads to reach the safepoint. Second, it lists how long it waited for threads to block. The third number is the total time waiting for threads to reach the safepoint (spin + block + some other time). Fourth, is the time spent in internal VM cleanup activities. Fifth, is the time spent in the operation itself.
DEBUGGING JVM SAFEPOINT PAUSES
而最后一個 0,說是 page_trap_count,暫時還不清楚是什么意思
最后,推薦知乎上講解 SafePoint 的帖子,用於幫助排查 STW 時間過長的問題,總結起來,就是分析 safepoint 的四個階段:Spin,Block,Cleanup,VM Operation 陳亮的回答
至此,安全點分析告一段落。
GC 打印控制
-verbose:gc
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-XX:+PringHeapAtGC
-XX:+PrintTenuringDistribution
大體上列出了這么多和打印 gc 日志相關的東西,這些是工作中常用的。
-XX:+PrintGC
一般要打印出 gc 日志,需要基本的配置:-verbose:gc
和-XX:+PrintGC
,這兩個沒有什么區別,一般采用后者。形式如下:
[GC 246656K->243120K(376320K), 0.0929090 secs]
[Full GC 243120K->241951K(629760K), 1.5589690 secs]
前面是類型,分為 GC 和 Full GC,以及堆大小的變化,耗費的時間。可以看到,這里並看不出是用的什么垃圾回收器,也不了解 Young 區和 Old 區的內存情況,更不能判斷垃圾回收器是否將一些對象從 Young 區轉到了 Old 區。
-XX:+PrintGCDetail
相比於 PrintGC 選項,這個會打印出更詳細的日志。在這個選項的模式下,日志的格式和使用的算法相關。例如
[GC [PSYoungGen: 142816K->10752K(142848K)] 246648K->243136K(375296K), 0.0935090 secs]
[Times: user=0.55 sys=0.10, real=0.09 secs]
從日志中可以看到所用的垃圾回收器,以及 Young GC 的作用,整個 Young 區的大小,回收后縮到了多少,整個堆的空間大小,以及變化情況,也能推出 Old 區的大小,甚至可以推出有多少對象從 Young 區轉移到 Old 區。同時,從 Times 中可以得知在垃圾收集線程和操作系統調用和等待系統事件所使用的時間(所有線程所花時間的總和),以及真實的時間,進而得知是否使用了多線程做了垃圾回收,這里可以看到,真實時間是遠小於前兩者的,實際上用了 8個線程。
再看一下 Full GC 的日志:
[Full GC[PSYoungGen: 10752K->9707K(142848K)][ParOldGen: 232384K->232244K(485888K)] 243136K->241951K(628736K)[PSPermGen: 3162K->3161K(21504K)], 1.5265450 secs]
這里可以看到 Young 和 Old 回收器,以及 Young 區和 Old 區的大小以及變化,同時還可以看到永久代的大小以及變化,這是 1.7 以下的 JDK 版本。后續的時間沒有列出,和上面貼出的 Young GC 日志類似,同樣可以看到那三個參數。
同時,Full GC 可以顯式的觸發,可以通過應用程序或者其他命令,這種日志的開頭會是Full GC(System)
,
-XX:+PrintGCTimeStamps & -XX:+PrintGCDateStamps
使用這兩個可以將時間和日期加到 GC 日志中。
2018-08-21T13:40:41.916+0800: 330.450: Total time for which application threads were stopped: 0.0013163 seconds, Stopping threads took: 0.0000636 seconds
2018-08-21T13:40:42.917+0800: 331.450: Application time: 1.0001349 seconds
還是這個例子,2018-08-21T13:40:41.916+0800:
是通過 PrintGCDateStamps 加入的,而 330.450
是通過 PrintGCTimeStamps 加入的,前者負責打印當前的時間,后者表示 JVM 啟動至今所經過的時間
-XX:+PrintHeapAtGC
如果我們設置了參數 -server -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution
參數,這里可以看到使用了 ParNew 作為 Young 區的收集器,使用 CMS 作為 Old 區的收集器,同時啟用了 PrintHeapAtGC 參數。打印出了如下的日志:
{Heap before GC invocations=3 (full 1):
par new generation total 2184576K, used 1795001K [0x0000000680000000, 0x0000000720000000, 0x0000000720000000)
eden space 1747712K, 100% used [0x0000000680000000, 0x00000006eaac0000, 0x00000006eaac0000)
from space 436864K, 10% used [0x0000000705560000, 0x000000070838e5b0, 0x0000000720000000)
to space 436864K, 0% used [0x00000006eaac0000, 0x00000006eaac0000, 0x0000000705560000)
concurrent mark-sweep generation total 2621440K, used 165820K [0x0000000720000000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 67048K, capacity 68642K, committed 68736K, reserved 1110016K
class space used 8241K, capacity 8546K, committed 8576K, reserved 1048576K
// 這部分不屬於 PrintHeapAtGC 打印的,稍后再講
2018-08-21T13:40:43.502+0800: 332.036: [GC (Allocation Failure) 332.036: [ParNew
Desired survivor size 223674368 bytes, new threshold 15 (max 15)
- age 1: 63634632 bytes, 63634632 total
- age 2: 1064928 bytes, 64699560 total
- age 3: 24489776 bytes, 89189336 total
: 1795001K->92332K(2184576K), 0.1389036 secs] 1960821K->258153K(4806016K), 0.1390975 secs] [Times: user=0.37 sys=0.16, real=0.14 secs]
Heap after GC invocations=4 (full 1):
par new generation total 2184576K, used 92332K [0x0000000680000000, 0x0000000720000000, 0x0000000720000000)
eden space 1747712K, 0% used [0x0000000680000000, 0x0000000680000000, 0x00000006eaac0000)
from space 436864K, 21% used [0x00000006eaac0000, 0x00000006f04eb3f0, 0x0000000705560000)
to space 436864K, 0% used [0x0000000705560000, 0x0000000705560000, 0x0000000720000000)
concurrent mark-sweep generation total 2621440K, used 165820K [0x0000000720000000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 67048K, capacity 68642K, committed 68736K, reserved 1110016K
class space used 8241K, capacity 8546K, committed 8576K, reserved 1048576K
}
如果設置了 PrintHeapAtGC 參數,則 HotSpot 在 GC 前后都會將 GC 堆的概要信息輸出出來。Heap before GC
和 Heap after GC
分別表示 GC 前后堆的信息的開始,invocations 表示 GC 的次數,可以看到 后面跟了個 invocations,這里 invocations 表示總的 GC 次數,可以發現在 after 之后,invocations 自增了,而 full 表示第幾次 Full GC。 invocations 會隨着系統運行一直自增下去,通過這些信息可以很輕松的統計出一段時間的 GC 次數。再看下面的日志,可以看到年輕代和老年代所使用的垃圾回收器,以及各自的情況。其中新生代 par new generation 表示使用 ParNew 作為垃圾回收器,一共 2184576 K 大小,使用了 1795001 K 大小。其中 eden 區已經滿了,from survivor 用了 10%,to survivor 用了 0%,每個后面都跟了內存地址,頭一個表示起始地址,第二個表示當前用到的最大地址,第三個表示終止地址。觀察 before 和 after,細心點可以觀察到 from 和 to 的地址對調了,可以看到回收一次后 from 從 10% 漲到了 21%。緊跟着 par new generation 后面的是 concurrent mark-sweep generation,總共的量,使用的量,地址可以清楚的看到,后面跟着的三個參數同樣是起止地址,而第二個和第三個是相同的。此外還給出了 Metaspace 的使用情況,以及 class space 的使用情況。這兩個值初始會比較小,在使用過程中會容量會逐步擴大。
-XX:+PrintTenuringDistribution
這個參數是負責打印新生代到老年代晉升的情況。我們需要知道,我們是可以通過 MaxTenuringThreshold 參數控制對象從新生代晉升到老年代經過 GC 次數的最大值,這個默認值是 15,而最大值也是 15,因為對象頭里給了 4個 bit 存放,只能表示 15 以內的整數。這個參數並非能達到絕對控制,比如晉升失敗會導致對象原地不動,如果 survival 區不夠大,可能直接放到老年代。再看它所輸出的信息:
2018-08-21T13:40:43.502+0800: 332.036: [GC (Allocation Failure) 332.036: [ParNew
Desired survivor size 223674368 bytes, new threshold 15 (max 15)
- age 1: 63634632 bytes, 63634632 total
- age 2: 1064928 bytes, 64699560 total
- age 3: 24489776 bytes, 89189336 total
: 1795001K->92332K(2184576K), 0.1389036 secs] 1960821K->258153K(4806016K), 0.1390975 secs] [Times: user=0.37 sys=0.16, real=0.14 secs]
好吧,接着看,GC 的觸發條件給出了, Allocation Failure,也就是分配內存失敗了,這時候 survivor 所期待的大小是 223674368 字節,最大的年代數是 15,當前 age1 有 63634632 字節,age2 有 1064928 字節,age3 有 24489776 字節,total 是當前加起來的總和。后續跟的和 PrintGCDetail 類似,有各個區的內存變化,以及所用的時間,這里不再多做解釋。
貼出來網上一個人的問帖,這里面有一個有趣的問題 How to read the output of +PrintTenuringDistribution
2013-10-19T19:46:30.244+0800: 169797.045: [GC2013-10-19T19:46:30.244+0800:
169797.045: [ParNew
Desired survivor size 87359488 bytes, new threshold 4 (max 4)
- age 1: 10532656 bytes, 10532656 total
- age 2: 14082976 bytes, 24615632 total
- age 3: 15155296 bytes, 39770928 total
- age 4: 13938272 bytes, 53709200 total
: 758515K->76697K(853376K), 0.0748620 secs] 4693076K->4021899K(6120832K),
0.0756370 secs] [Times: user=0.42 sys=0.00, real=0.07 secs]
2013-10-19T19:47:10.909+0800: 169837.710: [GC2013-10-19T19:47:10.909+0800:
169837.711: [ParNew
Desired survivor size 87359488 bytes, new threshold 4 (max 4)
- age 1: 9167144 bytes, 9167144 total
- age 2: 9178824 bytes, 18345968 total
- age 3: 16101552 bytes, 34447520 total
- age 4: 21369776 bytes, 55817296 total
: 759449K->63442K(853376K), 0.0776450 secs] 4704651K->4020310K(6120832K),
0.0783500 secs] [Times: user=0.43 sys=0.00, real=0.07 secs]
這是他粘貼的日志,可以看到第二次 GC 后,原先的 age 1 從 10532656 降到了 age 2 的 91788824,這是可以理解的,因為一部分可能只存活了一代就銷毀了。而關於 age 2 晉升到 age 3 就很奇怪了,因為它漲了。給出的解釋是:在把對象拷貝到 survivor 區或者 old 區時,一些線程會競爭,而每個線程在競爭時就會增加一代,這是一個 bug。。。不知道有沒有最終被修復。
-Xloggc
缺省的 GC 日志是輸出到終端的,使用 -Xloggc 可以輸出到指定文件,
可管理的 JVM 參數
在 JVM 運行時,一些參數是可以動態的去修改的,用於打印出來更詳細的參數。所有以 PrintGC
開頭的都是可管理的參數。這樣在任何時候都可以開啟和關閉 GC 日志。比如我們可以用 JDK 自帶的 jinfo 工具來設置這些參數,或者是通過 JMX 客戶端調用 HotSpotDiagnostic MXBean 的 setVMOptions 方法來設置這些參數。
查看 JVM 參數
jinfo -flag <參數名> PID
例如:jinfo -flag MaxMetaspaceSize 18348
調整 JVM 參數
- 布爾類型:jinfo -flag [+|-]<參數名> PID
- 數字、字符串類型:jinfo -flag <參數名>=<值> PID
查看所有支持動態修改的 JVM 參數
java -XX:+PrintFlagsInitial | grep manageable
垃圾回收器日志解讀
前面介紹了基本的 gc 日志打印配置項以及日志內容,根據上述內容相信可以看懂大部分的 gc 日志了。然而,不同的 GC 算法所打印的日志也有一定區別,尤其是 CMS 的日志內容(這里沒有對 G1 做分析,因為還沒有實際使用過 G1)相對來說更為難懂,涉及到的配置參數也很多。所以這里暫不對其他的垃圾回收器的日志做詳細介紹,而是直接看 CMS 的日志。如果想要了解,可以看以下幾個示例:
CMS 日志解讀
下面貼出了 CMS 一次垃圾回收的日志,這里只 grep 了 CMS 相關的日志,其余的沒有展示。
// 初始標記(STW)
2018-08-21T13:35:33.467+0800: 22.000: [GC (CMS Initial Mark) [1 CMS-initial-mark: 165823K(2621440K)] 213112K(4806016K), 0.0150297 secs] [Times: user=0.04 sys=0.00, real=0.01 secs]
// 並發標記
2018-08-21T13:35:33.482+0800: 22.015: [CMS-concurrent-mark-start]
2018-08-21T13:35:33.509+0800: 22.042: [CMS-concurrent-mark: 0.027/0.027 secs] [Times: user=0.21 sys=0.00, real=0.03 secs]
// 並發預清理
2018-08-21T13:35:33.509+0800: 22.042: [CMS-concurrent-preclean-start]
2018-08-21T13:35:33.517+0800: 22.050: [CMS-concurrent-preclean: 0.009/0.009 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
2018-08-21T13:35:33.517+0800: 22.050: [CMS-concurrent-abortable-preclean-start]
CMS: abort preclean due to time 2018-08-21T13:35:38.914+0800: 27.447: [CMS-concurrent-abortable-preclean: 4.213/5.397 secs] [Times: user=4.42 sys=0.09, real=5.40 secs]
// 重新標記(STW)
2018-08-21T13:35:38.915+0800: 27.448: [GC (CMS Final Remark) [YG occupancy: 106747 K (2184576 K)]27.448: [Rescan (parallel) , 0.0186754 secs]27.467: [weak refs processing, 0.0379073 secs]27.505: [class unloading, 0.0410018 secs]27.546: [scrub symbol table, 0.0120421 secs]27.558: [scrub string table, 0.0015368 secs][1 CMS-remark: 165823K(2621440K)] 272571K(4806016K), 0.1168714 secs] [Times: user=0.12 sys=0.05, real=0.11 secs]
// 並發清除
2018-08-21T13:35:39.032+0800: 27.565: [CMS-concurrent-sweep-start]
2018-08-21T13:35:39.175+0800: 27.708: [CMS-concurrent-sweep: 0.143/0.143 secs] [Times: user=0.10 sys=0.23, real=0.15 secs]
// 並發重置
2018-08-21T13:35:39.175+0800: 27.708: [CMS-concurrent-reset-start]
2018-08-21T13:35:39.220+0800: 27.753: [CMS-concurrent-reset: 0.045/0.045 secs] [Times: user=0.06 sys=0.08, real=0.04 secs]
- 初始標記:多線程,user 和 real 可以看出。老年代容量 2621440K,已經占用 165823K,整個堆的大小 4806016K,使用了 213112K。暫停應用線程,睡了 0.01秒
- 並發標記,7個線程
- 並發預清理,至到Eden區占用量達到CMSScheduleRemarkEdenPenetration(默認50%),或達到5秒鍾。但是如果ygc在這個階段中沒有發生的話,是達不到理想效果的。此時可以指定CMSMaxAbortablePrecleanTime,但是,等待一般都不是什么好的策略,可以采用CMSScavengeBeforeRemark,使remark之前發生一次ygc,從而減少remark階段暫停的時間。
- 重新標記,STW 時間最長的階段,可以看到和之前初始標記結果一樣
- 並發清除,只看時間
- 並發重置,為下次 CMS 做准備,只看時間
JVM 配置示例
除了要看懂 gc 日志,還有另一個方面需要了解,進而分析內容,那就是 JVM 配置。配置一般可分為:內存划分;垃圾收集器配置;日志打印;jvm 其他相關配置。在這里給出一個 JVM 配置示例。
// 1. 內存大小分配
// - 堆大小
-XX:InitialHeapSize=5368709120
-XX:MaxHeapSize=5368709120
// - 新生代大小
-XX:NewSize=2684354560
-XX:MaxNewSize=2684354560
// -- survivor 大小
-XX:SurvivorRatio=4
// - 方法區大小
-XX:MetaspaceSize=134217728
-XX:MaxMetaspaceSize=268435456
// 2. 垃圾回收器
// - 新生代
-XX:+UseParNewGC
// - 老年代
-XX:+UseConcMarkSweepGC
// -- 允許並發標記,降低標記過程的停頓
-XX:+CMSParallelRemarkEnabled
// -- 並發線程數
-XX:ConcGCThreads=8
// -- 預留空間,超過這個比例就會觸發 Full GC,設置過大可能導致 Concurrent Mode Failure,進而觸發 Serial Old GC,記得那張圖嗎?CMS 是可以和 Serial Old 聯合使用的,后者作為備選方案
-XX:CMSInitiatingOccupancyFraction=80
// -- 上述情況不觸發 GC,而是開啟內存碎片整理,合並過程無法並發,時間比較長
-XX:+UseCMSCompactAtFullCollection
// -- 與上面配合使用,執行多少次不壓縮的 Full GC 后來一次壓縮整理,如果為 0 則表示每次都不壓縮
-XX:CMSFullGCsBeforeCompaction=0
// -- 讓 CMS 對永久代進行回收
-XX:+CMSClassUnloadingEnabled
// -- 允許觸發 Full GC,與 CMS 聯合使用(一些 NIO 框架會使用對外內存,顯式的調用 System.gc,進而影響服務性能,如果禁用,那么堆外內存就一直無法回收,開啟的話,影響性能,於是提供了這個參數和 CMS 配合使用,使得 Full GC 性能更快)
-XX:+ExplicitGCInvokesConcurrent
// 疑問,聽說過 TLAB,沒聽過 PLAB,PLAB 是晉升本地分配緩沖,是垃圾回收清理數據時基於線程分配的分區,應更是用於提升 CMS 效率的一個參數,進階調優知識
-XX:OldPLABSize=16
// 3. 打印控制
// - 基本控制
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
// - 堆信息(before after)
-XX:+PrintHeapAtGC
// - 年代分布
-XX:+PrintTenuringDistribution
// - 安全點
-XX:+PrintGCApplicationConcurrentTime
-XX:+PrintGCApplicationStoppedTime
// 4. 其他
// - 壓縮類指針
-XX:+UseCompressedClassPointers
// - 壓縮對象
-XX:+UseCompressedOops
參考資料: