最近在做 Java8 到 Java17 的遷移工作,前期做了一些準備,但是在升級過程還是有些問題,太emo了,一些信息記錄如下,分為幾個部分:
- 編譯相關
- 參數遷移相關
- 運行相關
前人栽樹后人乘涼,有需要升級的可以參考一下,避免踩坑。。。
編譯相關
JEP 320
在 Java11 中引入了一個提案 JEP 320: Remove the Java EE and CORBA Modules (openjdk.org/jeps/320) 提案,移除了 Java EE and CORBA 的模塊,如果項目中用到需要手動引入。比如代碼中用到了 javax.annotation.*
下的包:
publicabstractclassFridayAgent
@PreDestroy
publicvoiddestroy(){
agentClient.close();
}
}
在編譯時會找不到相關的類。這是因為 Java EE 已經在 Java 9
中被標記為 deprecated,Java 11 中被正式移除,可以手動引入 javax 的包:
javax.annotation
javax.annotation-api
1.3.2
使用了 sun.misc.* 下的包
比如 sun.misc.BASE64Encoder,這個簡單,替換一下工具類即可。
[ERROR]symbol:classBASE64Encoder
[ERROR]location:packagesun.misc
netty 低版本使用了 sun.misc.*,編譯錯誤信息如下
Causedby:java.lang.NoClassDefFoundError:Couldnotinitializeclassio.netty.util.internal.PlatformDependent0
atio.netty.util.internal.PlatformDependent.getSystemClassLoader(PlatformDependent.java:694)~[netty-all-4.0.42.Final.jar!/:4.0.42.Final]
對應的源碼如下:
/**
*The{@linkPlatformDependent}operationswhichrequiresaccessto{@codesun.misc.*}.
*/
finalclassPlatformDependent0{
}
https://github.com/netty/netty/issues/6855
lombok 使用了 com.sun.tools.javac.* 下的包
錯誤信息如下:
Failed to execute goal org.apache.maven.plugins3.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.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.24version>
dependency>
kotlin 版本限制
我們后端在很多年前就 all-in Kotlin,Kotlin 的升級也是我們的重中之重。
[ERROR] Failed to execute goal org.jetbrains.kotlin1.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
來做項目的依賴分析
這樣你就可以知道哪些庫需要做升級了。
基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
- 項目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
- 視頻教程:https://doc.iocoder.cn/video/
參數遷移
什么是 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
輸出結果如下:
可以看到日志輸出里,不僅有 GC 相關的日志,還有 os 線程相關的信息。事實上 java 的日志的生產者有非常多部分,比如 thread、class load、unload、safepoint、cds 等。
歸根到底,日志打印,需要回答清楚三個問題:
- what:要輸出什么信息(tag),以什么日志級別輸出(level)
- where:輸出到哪里(console 還是 file)
- decorators:日志如何
輸出什么信息(selectors)
首先來看 what 的部分,如何指定要輸出哪些信息,這個在 JVM 內部被稱之為 selectors。
JVM 采用的是
的形式來表示 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]UsingG1
[0.023s][debug][gc]ConcGCThreads:3offset22
[0.023s][debug][gc]ParallelGCThreads:10
[0.024s][debug][gc]Initializemarkstackwith4096chunks,maximum524288
這樣就輸出了 tag 為 gc,級別為 debug 的日志信息。
不過這里有一個比較坑的點是,這里的 tag 匹配規則是精確匹配,如果某條日志的 tag 是 gc,metaspace
,通過上面的規則是匹配不到的,我們可以手動指定的方式來輸出。
$java-Xlog:gc+metaspace-version
[0.022s][info][gc,metaspace]CDSarchive(s)mappedat:...size12443648.
[0.022s][info][gc,metaspace]Compressedclassspacemappedat:reservedsize:...
[0.022s][info][gc,metaspace]Narrowklassbase:...,Narrow
klassshift:0,Narrowklassrange:0x100000000
這里的 selector 也是可以進行組合的,不同的 selector 之間用逗號分隔即可。比如同時輸出 gc
和 gc+metaspace
這兩類 tag 的日志,就可以這么寫:
$java-Xlog:gc=debug,gc+metaspace-version
[0.020s][info][gc]UsingG1
[0.020s][debug][gc]ConcGCThreads:3offset22
[0.020s][debug][gc]ParallelGCThreads:10
[0.020s][debug][gc]Initializemarkstackwith4096chunks,maximum524288
[0.022s][info][gc,metaspace]CDSarchive(s)mappedat:
[0.022s][info][gc,metaspace]Compressedclassspacemappedat:
[0.022s][info][gc,metaspace]Narrowklassbase:0x0000000800000000
當然這么搞是很麻煩的,JVM 提供了通配符 *
來解決精確匹配的問題,比如我們想要所有 tag 為 gc 的日志,可以這么寫:
$java-Xlog:gc*=debug-version
[0.024s][debug][gc,heap]Minimumheap8388608
[0.024s][info][gc]UsingG1
[0.024s][debug][gc,heap,coops]Heapaddress:0x0000000707400000
[0.024s][debug][gc]ConcGCThreads:3offset22
[0.024s][debug][gc]ParallelGCThreads:10
[0.024s][debug][gc]Initializemarkstackwith4096chunks
[0.024s][debug][gc,ergo,heap]Expandtheheap.requestedexpansionamount:
[0.025s][debug][gc,heap,region]Activateregions[0,125)[0.025s][debug][gc,ihop]Targetoccupancyupdate:old:0B,new:262144000B
[0.025s][debug][gc,ergo,refine]InitialRefinementZones:green:2560
[0.026s][debug][gc,task]G1ServiceThread
[0.026s][debug][gc,task]G1ServiceThread(PeriodicGCTask)(register)
[0.026s][info][gc,init]Version:17.0.3+7(release)
...
如果只想要 INFO 級別的日志,則可以省略 level 的設置,使用 java -Xlog:gc* -version
即可。
如果想知道有哪些個性化的 tag 可以選擇,可以用 java -Xlog:help
來找到所有可用的 tag。
階段性小結
第二部分:輸出到哪里(output)
默認情況下,日志會輸出到 stdout,jvm 支持以下三種輸出方式:
- stdout
- stderr
- file
一般而言我們會把日志輸出到文件中,方便后續進一步分析
-Xlog:all=debug:file=/path_to_logs/app.log
還可以指定日志切割的大小和方式
-Xlogfile=/path_to_logs/app.log:filesize=104857600,filecount=5
第三部分:日志 decorators
每條日志除了正常的信息以外,還有不少日志相關的上下文信息,在 jvm 中被稱為 decorators
,有下面這些可選項。
比如可以用 java -Xlog:all=debuglevel,tags,time,uptime,pid -version
選項來打印日志。
[2022-06-15T1901.529+0800][0.001s][5235][info][os,thread]Threadattached
[2022-06-15T1901.529+0800][0.001s][5235][debug][os,thread]Thread5237stack...
[2022-06-15T1901.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:+PrintGCDetailsisdeprecated.Willuse-Xlog:gc*instead.
常見的標記為廢棄的參數還有 -XX:+PrintGC
和 -Xloggc:
,遷移前后的參數如下:
舊參數 | 新參數 |
---|---|
-XX:+PrintGCDetails | -Xlog:gc* |
-XX:+PrintGC | -Xlog:gc |
-Xloggc:
|
-Xlogfile= |
除此之外,大量的 GC 的參數被移除,比如常用的參數 -XX:+PrintTenuringDistribution
,Java17 會拒絕啟動
java-XX:+PrintTenuringDistribution-version
UnrecognizedVMoption'PrintTenuringDistribution'
Error:CouldnotcreatetheJavaVirtualMachine.
Error:Afatalexceptionhasoccurred.Programwillexit.
更詳細的移除的參數如下
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]Minimumheap8388608Initialheap268435456Maximumheap
hello,g1gc!
[12.263s][debug][gc,heap]GC(0)HeapbeforeGCinvocations=0(full0):
[12.265s][debug][gc,heap]GC(0)garbage-firstheap
[12.265s][debug][gc,heap]GC(0)regionsize2048K,1young(2048K)
[12.265s][debug][gc,heap]GC(0)Metaspaceused3678K
[12.265s][debug][gc,heap]GC(0)classspaceused300K
[12.280s][debug][gc,heap]GC(0)Uncommittableregionsaftershrink:124
雖然理解起來不太直觀,不過要記住 -XX:+PrintGCApplicationStoppedTime
和 -XX+PrintGCApplicationConcurrentTime
這兩個參數一起被 -Xlog:safepoint
取代。
還有一個常見的參數 -XX:+PrintAdaptiveSizePolicy
被 -Xlog:gc+ergo*=trace
取代,
[0.122s][debug][gc,ergo,refine]InitialRefinementZones:green:23,yellow:
69,red:115,minyellowsize:46
[0.142s][debug][gc,ergo,heap]Expandtheheap.requestedexpansionamount:268435456Bexpansionamount:268435456B
[2.475s][trace][gc,ergo,cset]GC(0)StartchoosingCSet.pendingcards:0predictedbasetime:10.00msremainingtime:
190.00mstargetpausetime:200.00ms
[2.476s][trace][gc,ergo,cset]GC(9)AddyoungregionstoCSet.eden:24regions,survivors:0regions,predictedyoung
regiontime:367.19ms,targetpausetime:200.00ms
[2.476s][debug][gc,ergo,cset]GC(0)FinishchoosingCSet.old:0regions,predictedoldregiontime:0.00ms,time
remaining:0.00
[2.826s][debug][gc,ergo]GC(0)RunningG1ClearCardTableTaskusing1workersfor1unitsofworkfor24regions.
[2.827s][debug][gc,ergo]GC(0)RunningG1FreeCollectionSetusing1workersforcollectionsetlength24
[2.828s][trace][gc,ergo,refine]GC(0)UpdatingRefinementZones:updaterstime:0.004ms,updatersbuffers:0,updaters
goaltime:19.999ms
[2.829s][debug][gc,ergo,refine]GC(0)UpdatedRefinementZones:green:23,yellow:69,red:115
[3.045s][trace][gc,ergo,set]GC(1)StartchoosingCSet.pendingcards:5898predictedbasetime:26.69msremaining
time:173.31mstargetpausetime:200.00ms
[3.045s][trace][gc,ergo,cset]GC(1)AddyoungregionstoSet.eden:9regions,survivors:3regions,predictedyoung
regiontime:457.38ms,targetpausetime:200.00ms
[3.045s][debug](gc,ergo,set]GC(1)FinishchoosingCSet.old:@regions,predictedoldregiontime:0.00ms,time
remaining:0.00
[3.090s][debug][gc,ergo
]GC(1)RunningG1ClearCardTableTaskusing1workersfor1unitsofworkfor12regions.
[3.091s][debug][gc,ergo
GC(1)RunningG1FreeCollectionSetusing1workersforcollectionsetlength12
[3.093s][trace][gc,ergo,refine]GC(1)UpdatingRefinementZones:updaterstime:2.510ms,updatersbuffers:25,updaters
goaltime:19.999ms
[3.093s][debug][gc,ergo,refine]GC(1)UpdatedRefinementZones:green:25,yellow:75,red:125
看一下這部分的源碼的變遷,就可以知道確實是如此了,在 Java8 中,PSYoungGen::resize_spaces
代碼如下:
在 Java17 中,這部分日志打印被 gc+ergo 的標簽日志取代:
還有一個分代 GC 中非常有用的參數 -XX:+PrintTenuringDistribution
,現在被 gc+age=trace
取代
完整的參數變遷對應表如下:
舉例
-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
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
運行相關
反射+私有 API 調用之傷
在 Java8 中,沒有人能阻止你訪問特定的包,比如 sun.misc,對反射也沒有限制,只要 setAccessible(true) 就可以了。Java9 模塊化以后,一切都變了,只能通過 --add-exports
和 --add-opens
來打破模塊封裝
-
--add-opens
導出特定的包 -
--add-opens
允許模塊中特定包的類路徑深度反射訪問
比如:
--add-opensjava.base/java.lang=ALL-UNNAMED
--add-opensjava.base/java.io=ALL-UNNAMED
--add-opensjava.base/java.math=ALL-UNNAMED
--add-opensjava.base/java.net=ALL-UNNAMED
--add-opensjava.base/java.nio=ALL-UNNAMED
--add-opensjava.base/java.security=ALL-UNNAMED
--add-opensjava.base/java.text=ALL-UNNAMED
--add-opensjava.base/java.time=ALL-UNNAMED
--add-opensjava.base/java.util=ALL-UNNAMED
--add-opensjava.base/jdk.internal.access=ALL-UNNAMED
--add-opensjava.base/jdk.internal.misc=ALL-UNNAMED
關于 GC 算法的選擇
CMS 正式退出歷史舞臺,G1 正式接棒,ZGC 蓄勢待發。在GC 算法的選擇上,目前來看 G1 還是最佳的選擇,ZGC 因為有內存占用被 OS 標記過高(三倍共享內存)虛高的問題,進程可能被 OOM-killer 殺掉。
ZGC 三倍 RES 內存
ZGC 底層用到了一個稱之為染色指針的技術,使用三個視圖(Marked0、Marked1 和 Remapped)來映射到同一塊共享內存區域,原理如下:
##include
##include
##include
##include
##include
##include
##include
intmain(){
//shm_open()函數用來打開或者創建一個共享內存區,兩個進程可以通過給shm_open()函數傳遞相同的名字以達到操作同一共享內存的目的
intfd=::shm_open("/test",O_RDWR|O_CREAT|O_EXCL,0600);
if(fd0){
shm_unlink("/test");
perror("shmopenfailed");
return0;
}
size_tsize=1*1024*1024*1024;
//創建一個共享內存后,默認大小為0,所以需要設置共享內存大小。ftruncate()函數可用來調整文件或者共享內存的大小
::ftruncate(fd,size);
intprot=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("Addressofaddr1:%p,valueis0x%x
",p1,*p1);
::printf("Addressofaddr2:%p,valueis0x%x
",p2,*p2);
::printf("Addressofaddr3:%p,valueis0x%x
",p3,*p3);
::getchar();
*p2=0xcafebaba;
::printf("Addressofaddr1:%p,valueis0x%x
",p1,*p1);
::printf("Addressofaddr2:%p,valueis0x%x
",p2,*p2);
::printf("Addressofaddr3:%p,valueis0x%x
",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:+UseZGCMyTest
java-XX:+AlwaysPreTouch-Xms2G-Xmx2G-XX:+UseG1GCMyTest
接下面我們討論的都是 G1 相關的。
G1 參數調整
不要配置新生代的大小
這個在《JVM G1 源碼分析和調優》一書里有詳細的介紹,有兩個主要的原因:
- G1對內存的管理是不連續的,重新分配一個分區代價很低
- G1 的需要根據目標停頓時間動態調整搜集的分區的個數,如果不能調整新生代的大小,那么 G1 可能不能滿足停頓時間的要求
諸如 -Xmn, -XX:NewSize, -XX:MaxNewSize, -XX:SurvivorRatio
都不要在 G1 中出現,只需要控制最大、最小堆和目標暫停時間即可
調整 -XX:InitiatingHeapOccupancyPercent
到合適的值
IHOP 默認值為 45,這個值是啟動并發標記的先決條件,只有當老年代內存棧總空間的 45% 之后才會啟動并發標記任務。
增加這個值:導致并發標記可能花費更多的時間,同時導致 YGC 和 Mixed-GC 收集時的分區數變少,可以根據整體應用占用的平均內存來設置。
審核編輯 :李倩
-
JAVA
+關注
關注
19文章
2966瀏覽量
104702 -
編譯
+關注
關注
0文章
657瀏覽量
32852 -
遷移
+關注
關注
0文章
33瀏覽量
7925
原文標題:從 Java 8 升級到 Java 17 全過程,賊特么坑!
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論