jdk8升級(jí)到j(luò)dk11如何升級(jí)的真實(shí)案例(親身經(jīng)歷)
一、背景
為什么要升級(jí)JDK11
性能
- JDK11的G1的GC性能高很多,對(duì)比JDK8無論是性能還是內(nèi)存占比都有很大的提升,業(yè)內(nèi)各項(xiàng)數(shù)據(jù)指標(biāo)也都表明JDK11的G1在應(yīng)對(duì)突發(fā)流量的下的效果驚人;

- 版本兼容
- Spring Boot 2.7.x及以后的版本將不再支持Java 8作為最低版本。Spring Boot 2.6.x是最后一個(gè)正式支持Java 8的主線版本,一些新的中間件與組件也不再支持JDK8了;
- 必然趨勢(shì)
- JDK11(LTS)已經(jīng)成為業(yè)界主流,在Java開發(fā)社區(qū)和工業(yè)界中得到了廣泛的接受和使用;

二、升級(jí)前你要知道的點(diǎn)
- JDK11版本改動(dòng)較大,且不會(huì)向下兼容。所以當(dāng)你的業(yè)務(wù)代碼越復(fù)雜,調(diào)用的鏈路越多,升級(jí)的難度越大。你會(huì)遇到很多兼容性問題,比如 二方包不支持新版本JDK;
- JDK11移除了部分在Java 8就已經(jīng)標(biāo)記為過時(shí)的API例如sun.misc.Unsafe的部分方法,所以你的升級(jí)可能還涉及到代碼的改動(dòng);
- 驗(yàn)證是個(gè)漫長(zhǎng)而又耗時(shí)的過程,很多問題可能在運(yùn)行時(shí)階段才會(huì)暴露,你需要驗(yàn)證系統(tǒng)整體功能來保證系統(tǒng)穩(wěn)定;
三、升級(jí)過程
本地升級(jí),讓你的JDK11跑起來
- 本地JDK11下載
這里不過多闡述,需要注意區(qū)分JDK的arm版本與x64版本。
- IDEA選擇JDK11啟動(dòng)

- 框架升級(jí)
修改pom文件
<maven.compiler.target>11</maven.compiler.target> <maven.compiler.source>11</maven.compiler.source> <java.version>11</java.version> <spring-boot.version>2.1.6.RELEASE</spring-boot.version> <lombok.version>1.18.12</lombok.version>
軟件 | 最低版本 |
spring-boot | 2.1.x 開始支持jdk11 |
spring | 5.1.x |
idea | 2018.2 |
maven | 3.5.0 |
lombok | 1.18.x |
netty | 需要升級(jí)到 4.1.33.Final 或之后的版本,否則會(huì)引起堆外內(nèi)存增長(zhǎng) |
apache common lang3 | 3.12.0 |
jdk11已移除,需手工依賴二方庫(kù)
<dependency>
<groupId>javax.xml.soap</groupId>
<artifactId>javax.xml.soap-api</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>jaxws-ri</artifactId>
<version>2.3.3</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.sun.activation</groupId>
<artifactId>javax.activation</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.jvm</groupId>
<artifactId>java-migration-jdk-patch</artifactId>
<version>0.3.1</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
<version>1.2</version>
</dependency>- 遇到的問題
Deprecated: A global security auto-configuration is now provided

在Spring Boot 2.0及以上版本中,這個(gè)配置項(xiàng)已經(jīng)被廢棄并移除。如果你要關(guān)閉端點(diǎn)的安全性,需要在Spring Security的配置中對(duì)Actuator端點(diǎn)進(jìn)行配置。該配置項(xiàng)是默認(rèn)開啟安全檢測(cè)。
Dependency 'org.hibernate:hibernate-validator:' not found

需要指定版本號(hào)
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.4.Final</version>
</dependency>應(yīng)用不能進(jìn)行遠(yuǎn)程調(diào)試
原因分析
JDK 8 中 jdwp 默認(rèn)綁定的 host/ip 是 0.0.0.0,初于安全考慮在 JDK 9 后改成了 localhost(127.0.0.1),導(dǎo)出如果開發(fā)者在配置調(diào)試選項(xiàng)時(shí)只指定端口時(shí),在升級(jí)后無法進(jìn)行遠(yuǎn)程調(diào)試。
解決方案
指定調(diào)試選項(xiàng)時(shí)設(shè)置 host/ip 為 *,如:
agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000
或者 0.0.0.0,如:
agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000
其他問題
1、maven-compiler-plugin:此插件建議直接升級(jí)到最新版,同時(shí)在父Pom和每個(gè)你需要額外確定版本的包(比如說打給別人用的JDK8版本的包)里的Pom,指定版本:
<maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target>
2、Springboot和Spring版本:Spring從5.1開始支持11,Springboot從2.1.X開始支持11,我們的推薦是支持升級(jí)到當(dāng)前的最新版;
3、Netty因?yàn)槎淹鈨?nèi)存的釋放問題,請(qǐng)升級(jí)到4.1.33以上的版本;
4、lombok因?yàn)闀?huì)在編譯期插入自己的編譯邏輯,所以升級(jí)到11之后,需要將lombok升級(jí)到最新版,(編輯文檔時(shí)的最新版本是1.18.24);
5、可能大部分應(yīng)用都需要進(jìn)行Spring或者Springboot升級(jí),請(qǐng)務(wù)必做好回歸;
6、security-spring-boot-starter分為1.x.x和2.x.x版本,對(duì)應(yīng)springboot1和springboot2,請(qǐng)升級(jí)到2.x.x版本;
應(yīng)用部署發(fā)布
- 使用G1垃圾回收器
去除
#SERVICE_OPTS="${SERVICE_OPTS} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSMaxAbortablePrecleanTime=5000"
#SERVICE_OPTS="${SERVICE_OPTS} -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFractinotallow=80 -XX:+UseCMSInitiatingOccupancyOnly"
#SERVICE_OPTS="${SERVICE_OPTS} -XX:+ExplicitGCInvokesConcurrent -Dsun.rmi.dgc.server.gcInterval=2592000000 -Dsun.rmi.dgc.client.gcInterval=2592000000"
#SERVICE_OPTS="${SERVICE_OPTS} -XX:ParallelGCThreads=4"
#SERVICE_OPTS="${SERVICE_OPTS} -Xloggc:${MIDDLEWARE_LOGS}/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
SERVICE_OPTS="${SERVICE_OPTS} -XX:+UseG1GC -XX:+UseVtableBasedCHA -XX:+UseCompactObjectHeaders"
SERVICE_OPTS="${SERVICE_OPTS} -XX:G1HeapReginotallow=8m"
SERVICE_OPTS="${SERVICE_OPTS} -XX:+G1BarrierSkipDCQ"
SERVICE_OPTS="${SERVICE_OPTS} -Xlog:gc*:/home/admin/logs/gc.log:time"
SERVICE_OPTS="${SERVICE_OPTS} -XX:G1HeapWastePercent=2"
SERVICE_OPTS="${SERVICE_OPTS} -XX:+ExplicitGCInvokesConcurrent -Dsun.rmi.dgc.server.gcInterval=2592000000 -Dsun.rmi.dgc.client.gcInterval=2592000000"
if [ -n "$AJDK_MAX_PROCESSORS_LIMIT" ]; then
SERVICE_OPTS="${SERVICE_OPTS} -XX:ActiveProcessorCount=$AJDK_MAX_PROCESSORS_LIMIT"
fiGC調(diào)優(yōu)的注意事項(xiàng)(數(shù)據(jù)來源JVM團(tuán)隊(duì))
通常G1 GC是一個(gè)免調(diào)參的GC,并不需要額外的參數(shù)調(diào)整。老的一些的八股文Java GC調(diào)參經(jīng)驗(yàn)并不適用。
-Xmn參數(shù)一般不需要設(shè)置
G1預(yù)設(shè)了-XX:NewSize和-XX:MaxNewSize的值(不一致),會(huì)根據(jù)實(shí)際運(yùn)行來計(jì)算設(shè)置每次GC的young區(qū)的size,實(shí)現(xiàn)GC暫停的軟可控。
-XX:NewRatio同理不需要設(shè)置
-XX:SurvivorRatio一般也不設(shè)置
通常絕大部分使用者并不清楚這個(gè)參數(shù)的含義以及對(duì)GC帶來的影響,G1會(huì)自適應(yīng)處理這個(gè)參數(shù)相關(guān)的GC行為。
升級(jí)G1后可能需要關(guān)注的參數(shù)
-XX:MaxGCPauseMillis=N,G1暫停的目標(biāo)時(shí)間(毫秒)
默認(rèn)為200,很多用戶會(huì)刻意設(shè)小,通常情況下意義不大。G1實(shí)際的GC暫停任務(wù),并不會(huì)隨著暫停時(shí)間縮小而變少,可以設(shè)小會(huì)導(dǎo)致更頻繁的GC影響吞吐。一般不需要設(shè)置,如果需要更好的吞吐,通常是設(shè)置更大,保持young區(qū)不會(huì)縮減的太小。也可以咨詢JVM答疑專家考慮調(diào)整-XX:NewSize和-XX:MaxNewSize,來保持young 區(qū)的Size,維持合適的吞吐性能。
-XX:InitiatingHeapOccupancyPercent=N -XX:-G1UseAdaptiveIHOP (電商核心使用)
這兩個(gè)參數(shù)通常同時(shí)使用。JDK11引入了G1UseAdaptiveIHOP來提升老區(qū)的利用率(大堆,通常大幾十G,或者100G以上)。在我們?nèi)萜饕?guī)格中等規(guī)模的heap(通常5-20g)的size中,有時(shí)會(huì)出現(xiàn)old gc過于頻繁或者young gc過于頻繁的現(xiàn)象。因此考慮一個(gè)合適的靜態(tài)IHOP(老區(qū)使用占全堆比例觸發(fā)GC的閾值),會(huì)更加合適。
-XX:G1HeapRegionSize,(電商核心通常使用8m-32m)
設(shè)置G1 region的大小,應(yīng)對(duì)humongous對(duì)象(超過heap region size一半獨(dú)占一個(gè)或多個(gè)region)引起的GC異常。Heap region size默認(rèn)為Heap size/2048,如果默認(rèn)值過小,humongous對(duì)象分配過多,容易引起To-space exhausted的異常暫停時(shí)間:
[2024-01-05T14:14:31.817+0800] GC(266) To-space exhausted
-XX:G1HeapWastePercent,(默認(rèn)5,部分電商核心應(yīng)用設(shè)置為2)
G1在回收老區(qū)對(duì)象時(shí),可以允許5% heap size的垃圾對(duì)象不回收,來減少mixed GC的暫停開銷。當(dāng)Xmx10G時(shí),5%就有500m的空間,對(duì)于Java heap是一種浪費(fèi),因此可以考慮減少heap空間浪費(fèi)設(shè)置成2。不建議設(shè)置成0,可能會(huì)極大增加mixed GC的暫停。
-XX:G1MixedGCCountTarget,Mixed GC目標(biāo)次數(shù),默認(rèn)為8
實(shí)際的Mixed GC次數(shù)通常會(huì)小于G1MixedGCCountTarget,如果Concurrent mark/mixed gc的周期并不頻繁,單次mixed gc的暫停過長(zhǎng),通??梢钥紤]增大這個(gè)參數(shù),例如16,來分散單次mixed GC暫停的工作量,減少暫停時(shí)間。
升級(jí)G1的常見問題
CMS升級(jí)G1后,容器和Java進(jìn)程內(nèi)存占用變高
很多應(yīng)用在升級(jí)JDK11,出現(xiàn)容器和Java進(jìn)程內(nèi)存整體變高的現(xiàn)象,主要源自Heap的使用率差異。CMS的Old generation為非移動(dòng)式,由 CMSInitiatingOccupancyFraction 來控制使用比例來觸發(fā)gc,因此應(yīng)用啟動(dòng)后短時(shí)間內(nèi),heap old區(qū)使用率不會(huì)上升。而G1的heap region是松散管理,整體利用heap,所以顯得內(nèi)存使用率高。本質(zhì)是一個(gè)heap利用率的問題,cms初始留著部分heap不用。這個(gè)問題可以通過調(diào)低Xmx來解決(部分電商核心使用這個(gè)方案)。
GC日志中To-space exhausted引起的異常暫停
絕大部分是由于大對(duì)象分配過多,GC日志中頻繁出現(xiàn)
Pause Young (Concurrent Start) (G1 Humongous Allocation)
大對(duì)象分配過多,會(huì)導(dǎo)致堆空間快速被占滿,GC是出現(xiàn)To-space exhausted/evacuation failure,需要額外的暫停時(shí)間處理,甚至出現(xiàn)更耗時(shí)的Full GC全堆整理。
GC過于頻繁
相比傳統(tǒng)的CMS/Parallel GC,固定的young 區(qū)size。G1的young區(qū)size是自動(dòng)調(diào)整的,當(dāng)為了滿足暫停要求時(shí),會(huì)縮小young區(qū),導(dǎo)致GC頻率過高。一般的情況是避免MaxGCPauseMillis設(shè)置過小,參考上面參數(shù)的介紹。或者增大MaxGCPauseMillis的配置,同時(shí)有必要的話咨詢答疑專家,調(diào)整-XX:NewSize和-XX:MaxNewSize。
Mixed GC暫停過長(zhǎng)
G1除了整理清除young區(qū)對(duì)象的young GC,還有在Concurrent mark之后,包含整理老區(qū)對(duì)象的mixed gc。因此通常mixed GC會(huì)有更長(zhǎng)的暫停時(shí)間。如果單次mixed GC暫停過長(zhǎng),考慮增大上面介紹的參數(shù)G1MixedGCCountTarget,來進(jìn)一步分散老區(qū)對(duì)象整理的任務(wù),降低暫停
四、升級(jí)效果
日常運(yùn)行

可以看到在日常運(yùn)行中,G1的垃圾回收耗時(shí)也有不錯(cuò)的提升
壓測(cè)效果
相同壓測(cè)條件下TPS20


可以明顯看到GC耗時(shí)降低了不少,速度快了70%左右

線上運(yùn)行情況

從圖中可以看到Y(jié)GC的耗時(shí)明顯縮短,性能將近提升50%!這歸功于分代收集的能力
YGC平均暫停時(shí)間 | YGC次數(shù) | 效果 | |
JDK8+CMS | 7.4ms | 10347 | |
JDK11+G1 | 3.74ms | 10649 | 性能提升49.5% |
五、JDK11新玩法
字符串String加強(qiáng)
String str = " i am lzc "; boolean isblank = str.isBlank(); //判斷字符串是空白 boolean isempty = str.isEmpty(); //判斷字符串是否為空 String result1 = str.strip(); //首位空白 String result2 = str.stripTrailing(); //去除尾部空白 String result3 = str.stripLeading(); //去除首部空白 String copyStr = str.repeat(2); //復(fù)制幾遍字符串 long lineCount = str.lines().count(); //行數(shù)統(tǒng)計(jì) System.out.println(isblank); //結(jié)果:false System.out.println(isempty); //結(jié)果:false System.out.println(result1); //結(jié)果:i am lzc System.out.println(result2); //結(jié)果: i am lzc System.out.println(result3); //結(jié)果:i am lzc System.out.println(copyStr); //結(jié)果: i am lzc i am lzc System.out.println(lineCount); //結(jié)果:1
文件Files方法加強(qiáng)
Path filePath = Files.writeString(Path.of("/temp/a.txt"), "Sample text");
String fileContent = Files.readString(filePath);
System.out.println(fileContent.equals("Sample text"));數(shù)據(jù)流Stream方法加強(qiáng)
//Stream,允許接受一個(gè)null值,計(jì)算count時(shí),返回0
long count = Stream.ofNullable(null).count();
System.out.println(count); // 0
//方法都接受一個(gè)謂詞來決定從流中放棄哪些元素
//通俗理解:從集合中刪除滿足條件的元素,直到不滿足為止
List list1 = Stream.of(1, 2, 3, 2, 1)
.dropWhile(n -> n < 3)
.collect(Collectors.toList());
System.out.println(list1); // [3, 2, 1]
//方法都接受一個(gè)謂詞來決定從流中選用哪些元素
//通俗理解:從集合中提取滿足條件的元素,直到不滿足為止
List list2 = Stream.of(1, 2, 3, 2, 1)
.takeWhile(n -> n < 3)
.collect(Collectors.toList());
System.out.println(list2); // [1, 2]集合List、Map等方法加強(qiáng)
List list1 = List.of(1, 3, 5, 7);
List list2 = List.copyOf(list1);
System.out.println(list2); //結(jié)果: [1,3,5,7]
Map<Integer, String> map1 = Map.of(1, "a", 2, "b", 3, "c");
Map<Integer, String> map2 = Map.copyOf(map1);
System.out.println(map2); //結(jié)果: {1=a, 2=b, 3=c}optional加強(qiáng)
//新增orElseThrow,為空時(shí)拋異常
Object v2 = Optional.ofNullable(null).orElseThrow(); //結(jié)果:拋異常
//新增ifPresentOrElse,不為null執(zhí)行第1個(gè)回調(diào)函數(shù),為null時(shí)執(zhí)行第2個(gè)回調(diào)函數(shù)
Optional.ofNullable(null).ifPresentOrElse(
(x) -> {
System.out.println("數(shù)據(jù):" + x);
}, () -> {
System.out.println("數(shù)據(jù)不存在");
});
//提供另一個(gè)Optionals 作為空Optionals的回調(diào)
Object v3 = Optional.ofNullable(null)
.or(() -> Optional.of("fallback"))
.get(); //結(jié)果:fallback
System.out.println(v3);HTTP Client
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
// 異步
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
// 同步
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());六、常見問題解決
- JVM參數(shù)調(diào)整:移除
-Xverify等廢棄參數(shù),替換為--add-opens解決模塊訪問限制。 - 類路徑?jīng)_突:JDK 11默認(rèn)不包含
javax.xml等包,需顯式添加依賴(如--add-modules=jdk.xml.dom)。
七、總結(jié)
升級(jí)后需全面回歸測(cè)試,建議先在非生產(chǎn)環(huán)境驗(yàn)證。建議使用jdeprscan:檢測(cè)廢棄API。
到此這篇關(guān)于jdk8升級(jí)到j(luò)dk11如何升級(jí)的真實(shí)案例(親身經(jīng)歷)的文章就介紹到這了,更多相關(guān)jdk8升級(jí)到j(luò)dk11內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java統(tǒng)計(jì)字符串單詞個(gè)數(shù)的方法解析
在一些項(xiàng)目中可能需要對(duì)一段字符串中的單詞進(jìn)行統(tǒng)計(jì),本文在這里分享了一個(gè)簡(jiǎn)單的demo,有需要的朋友可以拿去看一下2017-01-01
Spring中的@PostConstruct注解使用方法解析
這篇文章主要介紹了Spring中的@PostConstruct注解使用方法解析,@PostConstruct注解是用來處理在@Autowired注入屬性后init()方法之前,對(duì)一些零散的屬性進(jìn)行賦值的注解,需要的朋友可以參考下2023-11-11
應(yīng)用Java泛型和反射導(dǎo)出CSV文件的方法
這篇文章主要介紹了應(yīng)用Java泛型和反射導(dǎo)出CSV文件的方法,通過一個(gè)自定義函數(shù)結(jié)合泛型與反射的應(yīng)用實(shí)現(xiàn)導(dǎo)出CSV文件的功能,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2014-12-12
FeignClient設(shè)置動(dòng)態(tài)url方式
文章介紹了如何在Spring Cloud環(huán)境下使用FeignClient實(shí)現(xiàn)負(fù)載均衡,通過配置Nacos和FeignClient屬性,可以實(shí)現(xiàn)服務(wù)間的負(fù)載均衡調(diào)用2024-11-11
Java編程實(shí)現(xiàn)比對(duì)兩個(gè)文本文件并標(biāo)記相同與不同之處的方法
這篇文章主要介紹了Java編程實(shí)現(xiàn)比對(duì)兩個(gè)文本文件并標(biāo)記相同與不同之處的方法,涉及java針對(duì)文本文件的讀取、遍歷、判斷等相關(guān)操作技巧,需要的朋友可以參考下2017-10-10
MyBatis批量添加數(shù)據(jù)2種實(shí)現(xiàn)方法
這篇文章主要介紹了MyBatis批量添加數(shù)據(jù)2種實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
java利用JEXL實(shí)現(xiàn)動(dòng)態(tài)表達(dá)式編譯
這篇文章主要介紹了java利用JEXL實(shí)現(xiàn)動(dòng)態(tài)表達(dá)式編譯,系統(tǒng)要獲取多個(gè)數(shù)據(jù)源的數(shù)據(jù),并進(jìn)行處理,最后輸出多個(gè)字段。字段的計(jì)算規(guī)則一般是簡(jiǎn)單的取值最多加一點(diǎn)條件判斷,下面是具體的實(shí)現(xiàn)方法2021-04-04

