天天看點

Java8 到 Java17 更新指南(Bug大全)

Java8 到 Java17 更新指南(Bug大全)

Java8 到 Java17 更新指南(Bug大全)

文章目錄

  • ​​Java8 到 Java17 更新指南(Bug大全)​​
  • ​​編譯相關​​
  • ​​JEP 320​​
  • ​​使用了 sun.misc.* 下的包​​
  • ​​lombok 使用了 com.sun.tools.javac.* 下的包​​
  • ​​kotlin 版本限制​​
  • ​​廢棄依賴分析​​
  • ​​參數遷移​​
  • ​​什麼是 Unified Logging​​
  • ​​輸出什麼資訊(selectors)​​
  • ​​第二部分:輸出到哪裡(output)​​
  • ​​第三部分:日志 decorators​​
  • ​​Unified Logging 小結​​
  • ​​GC 參數遷移​​
  • ​​舉例​​
  • ​​推薦的配置​​
  • ​​運作相關​​
  • ​​反射+私有 API 調用之傷​​
  • ​​關于 GC 算法的選擇​​
  • ​​ZGC 三倍 RES 記憶體​​
  • ​​G1 參數調整​​
  • ​​不要配置新生代的大小​​
  • ​​調整 `-XX:InitiatingHeapOccupancyPercent` 到合适的值​​
  • ​​結語​​

Java 8 是舊時代的 Java 6,還不快更新 。最近在做 Java8 到 Java17 的遷移工作,前期做了一些準備,過程中的一些資訊記錄如下(持續更新。。。)

分為幾個部分:

  • 編譯相關
  • 參數遷移相關
  • 運作相關

編譯相關

JEP 320

在 Java11 中引入了一個提案 JEP 320: Remove the Java EE and CORBA Modules (​​openjdk.org/jeps/320)​​ 提案,移除了 Java EE and CORBA 的子產品,如果項目中用到需要手動引入。比如代碼中用到了 ​

​javax.annotation.*​

​ 下的包:

import javax.annotation.PreDestroy;
public abstract class FridayAgent 
    @PreDestroy
    public void destroy() {
        agentClient.close();
    }
}    
複制代碼      

在編譯時會找不到相關的類。這是因為 Java EE 已經在 ​

​Java 9​

​ 中被标記為 deprecated,Java 11 中被正式移除,可以手動引入 javax 的包:

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>
複制代碼      

使用了 sun.misc.* 下的包

比如 sun.misc.BASE64Encoder,這個簡單,替換一下工具類即可。

[ERROR]   symbol:   class BASE64Encoder
[ERROR]   location: package sun.misc
複制代碼      

netty 低版本使用了 sun.misc.*,編譯錯誤資訊如下

Caused by: java.lang.NoClassDefFoundError: Could not initialize class io.netty.util.internal.PlatformDependent0
        at io.netty.util.internal.PlatformDependent.getSystemClassLoader(PlatformDependent.java:694) ~[netty-all-4.0.42.Final.jar!/:4.0.42.Final]
複制代碼      

對應的源碼如下:

/**
 * The {@link PlatformDependent} operations which requires access to {@code sun.misc.*}.
 */
final class PlatformDependent0 {
}

https://github.com/netty/netty/issues/6855
複制代碼      

lombok 使用了 com.sun.tools.javac.* 下的包

錯誤資訊如下:

Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.2:compile (default-compile) on project encloud-common: Fatal error compiling: java.lang.ExceptionInInitializerError: Unable to make field private com.sun.tools.javac.processing.JavacProcessingEnvironment$DiscoveredProcessors com.sun.tools.javac.processing.JavacProcessingEnvironment.discoveredProcs accessible: module jdk.compiler does not “opens com.sun.tools.javac.processing” to unnamed module

如果你的項目中使用 lombok,而且是低版本的話,就會出現,lombok 的原理是在編譯期做一些手腳,用到了 ​

​com.sun.tools.javac​

​ 下的檔案,更新到最新版可以解決。ps,個人很不喜歡 lombok, 調試的時候代碼和 class 對不上真的很惡心。

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
   <!-- <version>1.16.4</version>-->
    <version>1.18.24</version>
</dependency>
複制代碼      

kotlin 版本限制

我們後端在很多年前就 all-in Kotlin,Kotlin 的更新也是我們的重中之重。

[ERROR] Failed to execute goal org.jetbrains.kotlin:kotlin-maven-plugin:1.2.71:compile (compile) on project encloud-core: Compilation failure [ERROR] Unknown JVM target version: 17 [ERROR] Supported versions: 1.6, 1.8

Kotlin 在 1.6.0 版本開始支援 Java17 的位元組碼,低于 1.6.0 的編譯會直接報錯

廢棄依賴分析

可以用 ​

​jdeps --jdk-internals --multi-release 17 --class-path . encloud-api.jar​

​ 來做項目的依賴分析

Java8 到 Java17 更新指南(Bug大全)

這樣你就可以知道哪些庫需要做更新了。

參數遷移

什麼是 Unified Logging

在 Java 領域,有廣為人知的日志架構,slf4j、log4j 等,這些架構提供了統一的程式設計接口,讓使用者可以通過簡單的配置實作日志輸出的個性化配置,比如日志 tag、級别(info、debug 等)、上下文(線程 id、行号、時間等),在 JVM 内部之前一直缺乏這樣的規範,于是出來了 Unified Logging,實作了日志格式的大一統,這就是我們接下來要介紹的重點 ​

​Unified Logging​

​。

我們接觸最多的是 gc 的日志,在 java8 中,我們配置 gc 日志的參數是 ​

​-Xloggc:/tmp/gc.log​

​​。在 JVM 中除了 GC,還有大量的其它相關的日志,比如線程、os 等,在新的 Unified Logging 日志中,日志輸出的方式變更為了 ​

​java -Xlog:xxx​

​,GC 不再特殊隻是做為日志的一種存在形式。

java -Xlog -version
複制代碼      

輸出結果如下:

Java8 到 Java17 更新指南(Bug大全)

可以看到日志輸出裡,不僅有 GC 相關的日志,還有 os 線程相關的資訊。事實上 java 的日志的生産者有非常多部分,比如 thread、class load、unload、safepoint、cds 等。

Java8 到 Java17 更新指南(Bug大全)

歸根到底,日志列印,需要回答清楚三個問題:

  • what:要輸出什麼資訊(tag),以什麼日志級别輸出(level)
  • where:輸出到哪裡(console 還是 file)
  • decorators:日志如何

輸出什麼資訊(selectors)

首先來看 what 的部分,如何指定要輸出哪些資訊,這個在 JVM 内部被稱之為 selectors。

JVM 采用的是 ​

​<tag-set>=<level>​

​​的形式來表示 selectors,預設情況下,tag 為​

​all​

​​,表示所有的 tag,level 為 ​

​INFO​

​​,​

​java -Xlog -version​

​ 等價于下面的形式

java -Xlog:all=info -version
複制代碼      

如果我們想輸出tag 為 gc,日志級别為 debug 的日志,可以用 ​

​java -Xlog:gc=debug​

​ 的形式:

$ java -Xlog:gc=debug -version
[0.023s][info][gc] Using G1
[0.023s][debug][gc] ConcGCThreads: 3 offset 22
[0.023s][debug][gc] ParallelGCThreads: 10
[0.024s][debug][gc] Initialize mark stack with 4096 chunks, maximum 524288
複制代碼      

這樣就輸出了 tag 為 gc,級别為 debug 的日志資訊。

不過這裡有一個比較坑的點是,這裡的 tag 比對規則是精确比對,如果某條日志的 tag 是 ​

​gc,metaspace​

​,通過上面的規則是比對不到的,我們可以手動指定的方式來輸出。

$ java -Xlog:gc+metaspace -version

[0.022s][info][gc,metaspace] CDS archive(s) mapped at: ... size 12443648.
[0.022s][info][gc,metaspace] Compressed class space mapped at: reserved size:...
[0.022s][info][gc,metaspace] Narrow klass base:..., Narrow 
klass shift: 0, Narrow klass range: 0x100000000
複制代碼      

這裡的 selector 也是可以進行組合的,不同的 selector 之間用逗号分隔即可。比如同時輸出 ​

​gc​

​​ 和 ​

​gc+metaspace​

​ 這兩類 tag 的日志,就可以這麼寫:

$ java -Xlog:gc=debug,gc+metaspace -version

[0.020s][info][gc] Using G1
[0.020s][debug][gc] ConcGCThreads: 3 offset 22
[0.020s][debug][gc] ParallelGCThreads: 10
[0.020s][debug][gc] Initialize mark stack with 4096 chunks, maximum 524288
[0.022s][info ][gc,metaspace] CDS archive(s) mapped at:
[0.022s][info ][gc,metaspace] Compressed class space mapped at:
[0.022s][info ][gc,metaspace] Narrow klass base: 0x0000000800000000
複制代碼      

當然這麼搞是很麻煩的,JVM 提供了通配符 ​

​*​

​ 來解決精确比對的問題,比如我們想要所有 tag 為 gc 的日志,可以這麼寫:

$ java -Xlog:gc*=debug -version

[0.024s][debug][gc,heap] Minimum heap 8388608
[0.024s][info ][gc     ] Using G1
[0.024s][debug][gc,heap,coops] Heap address: 0x0000000707400000
[0.024s][debug][gc           ] ConcGCThreads: 3 offset 22
[0.024s][debug][gc           ] ParallelGCThreads: 10
[0.024s][debug][gc           ] Initialize mark stack with 4096 chunks
[0.024s][debug][gc,ergo,heap ] Expand the heap. requested expansion amount:
[0.025s][debug][gc,heap,region] Activate regions [0, 125)[0.025s][debug][gc,ihop       ] Target occupancy update: old: 0B, new: 262144000B
[0.025s][debug][gc,ergo,refine] Initial Refinement Zones: green: 2560
[0.026s][debug][gc,task       ] G1 Service Thread 
[0.026s][debug][gc,task       ] G1 Service Thread (Periodic GC Task) (register)
[0.026s][info ][gc,init       ] Version: 17.0.3+7 (release)
...
複制代碼      

如果隻想要 INFO 級别的日志,則可以省略 level 的設定,使用 ​

​java -Xlog:gc* -version​

​ 即可。

如果想知道有哪些個性化的 tag 可以選擇,可以用 ​

​java -Xlog:help​

​ 來找到所有可用的 tag。

階段性小結
Java8 到 Java17 更新指南(Bug大全)

第二部分:輸出到哪裡(output)

預設情況下,日志會輸出到 stdout,jvm 支援以下三種輸出方式:

  • stdout
  • stderr
  • file

一般而言我們會把日志輸出到檔案中,友善後續進一步分析

-Xlog:all=debug:file=/path_to_logs/app.log
複制代碼      

還可以指定日志切割的大小和方式

-Xlog:gc*:file=/path_to_logs/app.log:filesize=104857600,filecount=5
複制代碼      

第三部分:日志 decorators

每條日志除了正常的資訊以外,還有不少日志相關的上下文資訊,在 jvm 中被稱為 ​

​decorators​

​,有下面這些可選項。

Option Description
time Current time and date in ISO-8601 format.
uptime Time since the start of the JVM in seconds and milliseconds (e.g., 6.567s).
timemillis The same value as generated by System.currentTimeMillis().
uptimemillis Milliseconds since the JVM started.
timenanos The same value as generated by System.nanoTime().
uptimenanos Nanoseconds since the JVM started.
pid The process identifier.
tid The thread identifier.
level The level associated with the log message.
tags The tag-set associated with the log message.

比如可以用 ​

​java -Xlog:all=debug:stdout:level,tags,time,uptime,pid -version​

​ 選項來列印日志。

[2022-06-15T19:54:01.529+0800][0.001s][5235][info ][os,thread] Thread attached
[2022-06-15T19:54:01.529+0800][0.001s][5235][debug][os,thread] Thread 5237 stack...
[2022-06-15T19:54:01.529+0800][0.001s][5235][debug][perf,datacreation] 
複制代碼      

Unified Logging 小結

輸出格式如下:

-Xlog:[selectors]:[output]:[decorators][:output-options]
複制代碼      
  • selectors 是多個 tag 和 level 的組合,起到了 what(過濾器)的作用,格式為 ​

    ​tag1[+tag2...][*][=level][,...]​

  • decorators 是日志相關的描述資訊,也可以了解為上下文
  • output 是輸出相關的選項,一般我們會配置為輸出到檔案,按檔案大小切割

這裡補充一個知識點,就是預設值:

  • tag:all
  • level:info
  • output:stdout
  • decorators: uptime, level, tags

GC 參數遷移

可以看到 GC 相關的參數都已經收攏到 Xlog 下,以前的很多 Java8 下的參數已經被移除或者标記為過期。

比如 ​

​PrintGCDetails​

​​ 已經被 ​

​-Xlog:gc*​

​ 取代:

java -XX:+PrintGCDetails -version

[0.001s][warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead.
複制代碼      

常見的标記為廢棄的參數還有 ​

​-XX:+PrintGC​

​​ 和 ​

​-Xloggc:<filepath>​

​,遷移前後的參數如下:

舊參數 新參數
-XX:+PrintGCDetails -Xlog:gc*
-XX:+PrintGC -Xlog:gc
-Xloggc:​

​<filepath>​

-Xlog:gc:​

​file=<filepath>​

除此之外,大量的 GC 的參數被移除,比如常用的參數 ​

​-XX:+PrintTenuringDistribution​

​,Java17 會拒絕啟動

java -XX:+PrintTenuringDistribution -version
Unrecognized VM option 'PrintTenuringDistribution'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

複制代碼      

更詳細的移除的參數如下

CMSDumpAtPromotionFailure,
CMSPrintEdenSurvivorChunks,
GlLogLevel,
G1PrintHeapRegions, 
G1PrintRegionLivenessInfo, 
G1SummarizeConcMark,
G1SummarizeRSetStats,
G1TraceConcRefinement,
G1TraceEagerReclaimHumongousObjects, 
G1TraceStringSymbolTableScrubbing,
GCLogFileSize, NumberofGCLogFiles, 
PrintAdaptiveSizePolicy,
PrintclassHistogramAfterFullGC,
PrintClassHistogramBeforeFullGC,
PrintCMSInitiationStatistics
PrintCMSStatistics, 
PrintFLSCensus,
PrintFLSStatistics,
PrintGCApplicationConcurrentTime
PrintGCApplicationStoppedTime,
PrintGCCause,
PrintGCDateStamps, 
PrintGCID,
PrintGCTaskTimeStamps,
PrintGCTimeStamps,
PrintHeapAtGC,
PrintHeapAtGCExtended, 
PrintJNIGCStalls,
PrintOldPLAB
PrintParallel0ldGCPhaseTimes, 
PrintPLAB,
PrintPromotionFailure,
PrintReferenceGC, 
PrintStringDeduplicationStatistics, 
PrintTaskqueue,
PrintTenuringDistribution, 
PrintTerminationStats,
PrintTLAB,
TraceDynamicGCThreads,
TraceMetadataHumongousAllocation,
UseGCLogFileRotation,
VerifySilently
複制代碼      

這些移除的參數大部分都能在新的日志體系下找到對應的參數,比如 ​

​PrintHeapAtGC​

​​ 這個參數可以用 ​

​-Xlog:gc+heap=debug​

​ 來替代

$ java -Xlog:gc+heap=debug -cp . G1GCDemo01

[0.004s][debug][gc,heap] Minimum heap 8388608  Initial heap 268435456  Maximum heap 
hello, g1gc!
[12.263s][debug][gc,heap] GC(0) Heap before GC invocations=0 (full 0):
[12.265s][debug][gc,heap] GC(0)  garbage-first heap 
[12.265s][debug][gc,heap] GC(0)   region size 2048K, 1 young (2048K)
[12.265s][debug][gc,heap] GC(0)  Metaspace       used 3678K
[12.265s][debug][gc,heap] GC(0)   class space    used 300K
[12.280s][debug][gc,heap] GC(0) Uncommittable regions after shrink: 124

複制代碼      

雖然了解起來不太直覺,不過要記住 ​

​-XX:+PrintGCApplicationStoppedTime​

​​ 和 ​

​-XX+PrintGCApplicationConcurrentTime​

​​ 這兩個參數一起被 ​

​-Xlog:safepoint​

​ 取代。

還有一個常見的參數 ​

​-XX:+PrintAdaptiveSizePolicy​

​​ 被 ​

​-Xlog:gc+ergo*=trace​

​ 取代,

[0.122s][debug][gc, ergo, refine] Initial Refinement Zones: green: 23, yellow:
69, red: 115, min yellow size: 46
[0.142s ][debug][gc, ergo, heap ] Expand the heap. requested expansion amount: 268435456B expansion amount: 268435456B
[2.475s][trace][gc, ergo, cset] GC(0) Start choosing CSet. pending cards: 0 predicted base time: 10.00ms remaining time:
190.00ms target pause time: 200.00ms
[2.476s][trace][gc, ergo, cset ] GC(9) Add young regions to CSet. eden: 24 regions, survivors: 0 regions, predicted young
region time: 367.19ms, target pause time: 200.00ms
[2.476s ][debug][gc, ergo, cset ] GC(0) Finish choosing CSet. old: 0 regions, predicted old region time: 0.00ms, time
remaining: 0.00
[2.826s][debug][gc, ergo] GC(0) Running G1 Clear Card Table Task using 1 workers for 1 units of work for 24 regions.
[2.827s][debug][gc, ergo] GC (0) Running G1 Free Collection Set using 1 workers for collection set length 24
[2.828s][trace][gc, ergo, refine] GC(0) Updating Refinement Zones: update rs time: 0.004ms, update rs buffers: 0, update rs
goal time: 19.999ms
[2.829s][debug][gc, ergo, refine] GC(0) Updated Refinement Zones: green: 23, yellow: 69, red: 115
[3.045s][trace][gc, ergo, set ] GC(1) Start choosing CSet. pending cards: 5898 predicted base time: 26.69ms remaining
time: 173.31ms target pause time: 200.00ms
[3.045s][trace][gc, ergo, cset ] GC(1) Add young regions to Set. eden: 9 regions, survivors: 3 regions, predicted young
region time: 457.38ms, target pause time: 200.00ms
[3.045s][debug](gc, ergo, set ] GC(1) Finish choosing CSet. old: @ regions, predicted old region time: 0.00ms, time
remaining: 0.00
[3.090s ][debug][gc, ergo
] GC (1) Running G1 Clear Card Table Task using 1 workers for 1 units of work for 12 regions.
[3.091s][debug][gc, ergo
GC (1) Running G1 Free Collection Set using 1 workers for collection set length 12
[3.093s][trace][gc, ergo, refine] GC(1) Updating Refinement Zones: update rs time: 2.510ms, update rs buffers: 25, update rs
goal time: 19.999ms
[3.093s ][debug][gc, ergo, refine] GC(1) Updated Refinement Zones: green: 25, yellow: 75, red: 125
複制代碼      

看一下這部分的源碼的變遷,就可以知道确實是如此了,在 Java8 中,​

​PSYoungGen::resize_spaces​

​代碼如下:

Java8 到 Java17 更新指南(Bug大全)

在 Java17 中,這部分日志列印被 gc+ergo 的标簽日志取代:

Java8 到 Java17 更新指南(Bug大全)

還有一個分代 GC 中非常有用的參數 ​

​-XX:+PrintTenuringDistribution​

​​,現在被 ​

​gc+age=trace​

​ 取代

完整的參數變遷對應表如下:

舊 GC 參數 -XX:+… 對應新 GC 參數 GC 參數含義
PrintGC -Xloggc: gc Print message at garbage collection
PrintGCDetails -Xloggc: gc* Print more details at garbage collection
-verbose:gc gc=trace gc+heap=trace gc+heap+exit=trace gc+metaspace=trace gc+sweep=debug gc+heap+ergo=debug Verbose GC
PrintGCCause GC cause is now always logged Include GC cause in GC logging
PrintGCID GC ID is now always logged Print an identifier for each garbage collection
PrintGCApplicationStoppedTime safepoint Print the time the application has been stopped
PrintGCApplicationConcurrentTime safepoint Print the time the application has been running
PrintTenuringDistribution gc+age*=trace Print tenuring age information
PrintAdaptiveSizePolicy gc+ergo*=trace Print information about AdaptiveSizePolicy
PrintHeapAtGC gc+heap=debug Print heap layout before and after each GC
PrintHeapAtGCExtended gc+heap=trace Print extended information about the layout of the heap when -XX:+PrintHeapAtGC is set
PrintClassHistogramBeforeFullGC classhisto*=trace Print a class histogram before any major stop-world GC
PrintClassHistogramAfterFullGC classhisto*=trace Print a class histogram after any major stop-world GC
PrintStringDeduplicationStatistics gc+stringdedup*=debug Print string deduplication statistics
PrintJNIGCStalls gc+jni=debug Print diagnostic message when GC is stalled by JNI critical section
PrintReferenceGC gc+ref=debug Print times spent handling reference objects during GC
PrintGCTaskTimeStamps task*=debug Print timestamps for individual gc worker thread tasks
PrintTaskQueue gc+task+stats=trace Print taskqueue statistics for parallel collectors
PrintPLAB gc+plab=trace Print (survivor space) promotion LAB’s sizing decisions
PrintOldPLAB gc+plab=trace Print (old gen) promotion LAB’s sizing decisions
PrintPromotionFailure gc+promotion=debug Print additional diagnostic information following promotion failure
PrintTLAB gc+tlab=trace Print various TLAB related information (augmented with -XX:+TLABStats)
PrintTerminationStats gc+task+stats=debug Print termination statistics for parallel collectors
G1PrintHeapRegions gc+region=trace If set G1 will print information on which regions are being allocated and which are reclaimed
G1PrintRegionsLivenessInfo gc+liveness=trace Prints the liveness information for all regions in the heap at the end of a marking cycle
G1SummarizeConcMark gc+marking=trace Summarize concurrent mark info
G1SummarizeRSets gc+remset*=trace Summarize remembered set processing info
G1TraceConcRefinement gc+refine=debug Trace G1 concurrent refinement
G1TraceEagerReclaimHumongousObjects gc+humongous=debug Print some information about large object liveness at every young GC
G1TraceStringSymbolTableScrubbing gc+stringdedup=trace Trace information string and symbol table scrubbing
PrintParallelOldGCPhaseTimes gc+phases=trace Print the time taken by each phase in ParallelOldGC
CMSDumpAtPromotionFailure gc+promotion=trace Dump useful information about the state of the CMS old generation upon a promotion failure (complemented by flags CMSPrintChunksInDump or CMSPrintObjectsInDump)
CMSPrintEdenSurvivorChunks gc+heap=trace Print the eden and the survivor chunks used for the parallel initial mark or remark of the eden/survivor spaces
PrintCMSInitiationStatistics gc=trace Statistics for initiating a CMS collection
PrintCMSStatistics gc=debug (trace) gc+task=trace gc+survivor=trace log+sweep=debug (trace) Statistics for CMS (complemented by CMSVerifyReturnedBytes)
PrintFLSCensus gc+freelist+census=debug Census for CMS’ FreeListSpace
PrintFLSStatistics gc+freelist+stats=debug (trace) gc+freelist*=debug (trace) Statistics for CMS’ FreeListSpace
TraceCMSState gc+state=debug Trace the state of the CMS collection
TraceSafepoint safepoint=debug Trace application pauses due to VM operations in safepoints
TraceSafepointCleanupTime safepoint+cleanup=info break down of clean up tasks performed during safepoint
TraceAdaptativeGCBoundary heap+ergo=debug Trace young-old boundary moves
TraceDynamicGCThreads gc+task=trace Trace the dynamic GC thread usage
TraceMetadataHumongousAllocation gc+metaspace+alloc=debug Trace humongous metadata allocations
VerifySilently gc+verify=debug

舉例

-XX:+PrintGCDetails                           \  // gc*
-XX:+PrintGCApplicationStoppedTime            \  // safepoint
-XX:+PrintGCApplicationConcurrentTime         \  // safepoint 
-XX:+PrintGCCause                             \  // 預設會輸出
-XX:+PrintGCID                                \  // 預設會輸出
-XX:+PrintTenuringDistribution                \  // gc+age*=trace
-XX:+PrintGCDateStamps                        \  // :time,tags,level
-XX:+UseGCLogFileRotation                     \  // :filecount=5,filesize=10M 
-XX:NumberOfGCLogFiles=5                      \  // :filecount=5,filesize=10M 
-XX:GCLogFileSize=10M                         \  // :filecount=5,filesize=10M 
-Xloggc:/var/log/`date +%FT%H-%M-%S`-gc.log   \  // -Xlog::file=/var/log/%t-gc.log 
複制代碼      

變遷後:

-Xlog:
  gc*, 
  safepoint, 
  gc+heap=debug, 
  gc+ergo*=trace, 
  gc+age*=trace, 
  :file=/var/log/%t-gc.log 
  :time,tags,level 
  :filecount=5,filesize=10M 
複制代碼      

推薦的配置

-Xlog:
        // selections
    codecache+sweep*=trace,
    class+unload,                      // TraceClassUnloading
    class+load,                        // TraceClassLoading
    os+thread,
    safepoint,                        // TraceSafepoint
    gc*,                              // PrintGCDetails
    gc+stringdedup=debug,             // PrintStringDeduplicationStatistics
    gc+ergo*=trace,
    gc+age=trace,                     // PrintTenuringDistribution
    gc+phases=trace,
    gc+humongous=trace,
    jit+compilation=debug
// output
:file=/path_to_logs/app.log   
// decorators               
:level,tags,time,uptime,pid
// output-options                
:filesize=104857600,filecount=5
複制代碼      

運作相關

反射+私有 API 調用之傷

在 Java8 中,沒有人能阻止你通路特定的包,比如 sun.misc,對反射也沒有限制,隻要 setAccessible(true) 就可以了。Java9 子產品化以後,一切都變了,隻能通過 ​

​--add-exports​

​​ 和 ​

​--add-opens​

​ 來打破子產品封裝

  • ​--add-opens​

    ​ 導出特定的包
  • ​--add-opens​

    ​ 允許子產品中特定包的類路徑深度反射通路

比如:

--add-opens java.base/java.lang=ALL-UNNAMED 
--add-opens java.base/java.io=ALL-UNNAMED 
--add-opens java.base/java.math=ALL-UNNAMED 
--add-opens java.base/java.net=ALL-UNNAMED 
--add-opens java.base/java.nio=ALL-UNNAMED 
--add-opens java.base/java.security=ALL-UNNAMED 
--add-opens java.base/java.text=ALL-UNNAMED 
--add-opens java.base/java.time=ALL-UNNAMED 
--add-opens java.base/java.util=ALL-UNNAMED 
--add-opens java.base/jdk.internal.access=ALL-UNNAMED 
--add-opens java.base/jdk.internal.misc=ALL-UNNAMED
複制代碼      

關于 GC 算法的選擇

CMS 正式退出曆史舞台,G1 正式接棒,ZGC 蓄勢待發。在GC 算法的選擇上,目前來看 G1 還是最佳的選擇,ZGC 因為有記憶體占用被 OS 标記過高(三倍共享記憶體)虛高的問題,程序可能被 OOM-killer 殺掉。

ZGC 三倍 RES 記憶體

ZGC 底層用到了一個稱之為染色指針的技術,使用三個視圖(Marked0、Marked1 和 Remapped)來映射到同一塊共享記憶體區域,原理如下:

#include <iostream>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>

int main() {
    // shm_open()函數用來打開或者建立一個共享記憶體區,兩個程序可以通過給shm_open()函數傳遞相同的名字以達到操作同一共享記憶體的目的
    int fd = ::shm_open("/test", O_RDWR | O_CREAT | O_EXCL, 0600);
    if (fd < 0) {
        shm_unlink("/test");
        perror("shm open failed");
        return 0;
    }

    size_t size = 1 * 1024 * 1024 * 1024;
    // 建立一個共享記憶體後,預設大小為0,是以需要設定共享記憶體大小。ftruncate()函數可用來調整檔案或者共享記憶體的大小
    ::ftruncate(fd, size);
    int prot = PROT_READ | PROT_WRITE;
    // 建立共享記憶體後,需要将共享記憶體映射到調用程序的位址空間,可通過mmap()函數來完成
    uint32_t *p1 = (uint32_t *) (mmap(nullptr, size, prot, MAP_SHARED, fd, 0));
    uint32_t *p2 = (uint32_t *) (mmap(nullptr, size, prot, MAP_SHARED, fd, 0));
    uint32_t *p3 = (uint32_t *) (mmap(nullptr, size, prot, MAP_SHARED, fd, 0));
    ::close(fd);
    *p1 = 0xcafebabe;
    ::printf("Address of addr1: %p, value is 0x%x\n", p1, *p1);
    ::printf("Address of addr2: %p, value is 0x%x\n", p2, *p2);
    ::printf("Address of addr3: %p, value is 0x%x\n", p3, *p3);
    ::getchar();
    *p2 = 0xcafebaba;
    ::printf("Address of addr1: %p, value is 0x%x\n", p1, *p1);
    ::printf("Address of addr2: %p, value is 0x%x\n", p2, *p2);
    ::printf("Address of addr3: %p, value is 0x%x\n", p3, *p3);
    ::getchar();
    munmap(p1, size);
    munmap(p2, size);
    munmap(p3, size);
    shm_unlink("/test");
    std::cout << "hello" << std::endl;
}
複制代碼      

你可以想象 p1、p2、p3 這三塊記憶體區域就是 ZGC 中三種視圖。

但是在 linux 統計中,雖然是共享記憶體,但是依然會統計三次,比如 RES。

同一個應用,使用 G1 RES 顯示占用 2G,ZGC 則顯示占用 6G

java -XX:+AlwaysPreTouch -Xms2G -Xmx2G -XX:+UseZGC MyTest
java -XX:+AlwaysPreTouch -Xms2G -Xmx2G -XX:+UseG1GC MyTest                                                                                              
複制代碼      
Java8 到 Java17 更新指南(Bug大全)

接下面我們讨論的都是 G1 相關的。

G1 參數調整

不要配置新生代的大小

這個在《JVM G1 源碼分析和調優》一書裡有詳細的介紹,有兩個主要的原因:

  • G1對記憶體的管理是不連續的,重新配置設定一個分區代價很低
  • G1 的需要根據目标停頓時間動态調整搜集的分區的個數,如果不能調整新生代的大小,那麼 G1 可能不能滿足停頓時間的要求

諸如 ​

​-Xmn, -XX:NewSize, -XX:MaxNewSize, -XX:SurvivorRatio​

​ 都不要在 G1 中出現,隻需要控制最大、最小堆和目标暫停時間即可

調整 -XX:InitiatingHeapOccupancyPercent 到合适的值

IHOP 預設值為 45,這個值是啟動并發标記的先決條件,隻有當老年代記憶體棧總空間的 45% 之後才會啟動并發标記任務。

結語