切換語言為:簡體
JDK8升級JDK11詳細說明

JDK8升級JDK11詳細說明

  • 爱糖宝
  • 2024-05-28
  • 2140
  • 0
  • 0

本文記錄了作者從JDK8升級到11的實踐過程和升級後的效果以及JDK11新玩法。

一、背景

為什麼要升級JDK11

  • 效能

    • JDK11的G1的GC效能高很多,對比JDK8無論是效能還是記憶體佔比都有很大的提升,業內各項資料指標也都表明JDK11的G1在應對突發流量的下的效果驚人;

JDK8升級JDK11詳細說明

  • 版本相容

    •  Spring Boot 2.7.x及以後的版本將不再支援Java 8作為最低版本。Spring Boot 2.6.x是最後一個正式支援Java 8的主線版本,一些新的中介軟體與元件也不再支援JDK8了;

  • 必然趨勢

    • JDK11(LTS)已經成為業界主流,在Java開發社羣和工業界中得到了廣泛的接受和使用;

JDK8升級JDK11詳細說明

二、升級前你要知道的點

  • JDK11版本改動較大,且不會向下相容。所以當你的業務程式碼越複雜,呼叫的鏈路越多,升級的難度越大。你會遇到很多相容性問題,比如  二方包不支援新版本JDK;

  • JDK11移除了部分在Java 8就已經標記為過時的API例如sun.misc.Unsafe的部分方法,所以你的升級可能還涉及到程式碼的改動;

  • 驗證是個漫長而又耗時的過程,很多問題可能在執行時階段纔會暴露,你需要驗證系統整體功能來保證系統穩定;

三、升級過程

本地升級,讓你的JDK11跑起來

  • 本地JDK11下載

這裏不過多闡述,需要注意區分JDK的arm版本與x64版本。

  • IDEA選擇JDK11啟動

JDK8升級JDK11詳細說明

  • 框架升級 

修改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
需要升級到 4.1.33.Final 或之後的版本,否則會引起堆外記憶體增長
apache common lang3
3.12.0

jdk11已移除,需手工依賴二方庫

<dependency>
    <groupId>javax.xml.soap</groupId>
    <artifactId>javax.xml.soap-api</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>com.sun.xml.ws</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

JDK8升級JDK11詳細說明

在Spring Boot 2.0及以上版本中,這個配置項已經被廢棄並移除。如果你要關閉端點的安全性,需要在Spring Security的配置中對Actuator端點進行配置。該配置項是預設開啟安全檢測。

Dependency 'org.hibernate:hibernate-validator:' not found 

JDK8升級JDK11詳細說明

需要指定版本號

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.2.4.Final</version>
</dependency>

應用不能進行遠端除錯

原因分析

JDK 8 中 jdwp 預設繫結的 host/ip 是 0.0.0.0,初於安全考慮在 JDK 9 後改成了 localhost(127.0.0.1),匯出如果開發者在配置除錯選項時只指定埠時,在升級後無法進行遠端除錯。

解決方案

指定除錯選項時設定 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:此外掛建議直接升級到最新版,同時在父Pom和每個你需要額外確定版本的包(比如說打給別人用的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,我們的推薦是支援升級到當前的最新版;

3、Netty因為堆外記憶體的釋放問題,請升級到4.1.33以上的版本;

4、lombok因為會在編譯期插入自己的編譯邏輯,所以升級到11之後,需要將lombok升級到最新版,(編輯文件時的最新版本是1.18.24);

5、可能大部分應用都需要進行Spring或者Springboot升級,請務必做好迴歸;

6、security-spring-boot-starter分為1.x.x和2.x.x版本,對應springboot1和springboot2,請升級到2.x.x版本;

應用部署釋出

  • 使用G1垃圾回收器

去除
#SERVICE_OPTS="${SERVICE_OPTS} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSMaxAbortablePrecleanTime=5000"
#SERVICE_OPTS="${SERVICE_OPTS} -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFraction=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:G1HeapRegionSize=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"
fi

GC調優的注意事項(資料來源JVM團隊)

通常G1 GC是一個免調參的GC,並不需要額外的引數調整。老的一些的八股文Java GC調參經驗並不適用。

-Xmn引數一般不需要設定

G1預設了-XX:NewSize和-XX:MaxNewSize的值(不一致),會根據實際執行來計算設定每次GC的young區的size,實現GC暫停的軟可控。

-XX:NewRatio同理不需要設定

-XX:SurvivorRatio一般也不設定

通常絕大部分使用者並不清楚這個引數的含義以及對GC帶來的影響,G1會自適應處理這個引數相關的GC行為。

升級G1後可能需要關注的引數

-XX:MaxGCPauseMillis=N,G1暫停的目標時間(毫秒)

預設為200,很多使用者會刻意設小,通常情況下意義不大。G1實際的GC暫停任務,並不會隨著暫停時間縮小而變少,可以設小會導致更頻繁的GC影響吞吐。一般不需要設定,如果需要更好的吞吐,通常是設定更大,保持young區不會縮減的太小。也可以諮詢JVM答疑專家考慮調整-XX:NewSize和-XX:MaxNewSize,來保持young 區的Size,維持合適的吞吐效能。

-XX:InitiatingHeapOccupancyPercent=N -XX:-G1UseAdaptiveIHOP (電商核心使用)

這兩個引數通常同時使用。JDK11引入了G1UseAdaptiveIHOP來提升老區的利用率(大堆,通常大幾十G,或者100G以上)。在我們容器規格中等規模的heap(通常5-20g)的size中,有時會出現old gc過於頻繁或者young gc過於頻繁的現象。因此考慮一個合適的靜態IHOP(老區使用佔全堆比例觸發GC的閾值),會更加合適。

-XX:G1HeapRegionSize,(電商核心通常使用8m-32m)

設定G1 region的大小,應對humongous物件(超過heap region size一半獨佔一個或多個region)引起的GC異常。Heap region size預設為Heap size/2048,如果預設值過小,humongous物件分配過多,容易引起To-space exhausted的異常暫停時間:

[2024-01-05T14:14:31.817+0800] GC(266) To-space exhausted

-XX:G1HeapWastePercent,(預設5,部分電商核心應用設定為2)

G1在回收老區物件時,可以允許5% heap size的垃圾物件不回收,來減少mixed GC的暫停開銷。當Xmx10G時,5%就有500m的空間,對於Java heap是一種浪費,因此可以考慮減少heap空間浪費設定成2。不建議設定成0,可能會極大增加mixed GC的暫停。

-XX:G1MixedGCCountTarget,Mixed GC目標次數,預設為8

實際的Mixed GC次數通常會小於G1MixedGCCountTarget,如果Concurrent mark/mixed gc的週期並不頻繁,單次mixed gc的暫停過長,通常可以考慮增大這個引數,例如16,來分散單次mixed GC暫停的工作量,減少暫停時間。

升級G1的常見問題

CMS升級G1後,容器和Java程序記憶體佔用變高

很多應用在升級JDK11,出現容器和Java程序記憶體整體變高的現象,主要源自Heap的使用率差異。CMS的Old generation為非移動式,由 CMSInitiatingOccupancyFraction 來控制使用比例來觸發gc,因此應用啟動後短時間內,heap old區使用率不會上升。而G1的heap region是鬆散管理,整體利用heap,所以顯得記憶體使用率高。本質是一個heap利用率的問題,cms初始留著部分heap不用。這個問題可以透過調低Xmx來解決(部分電商核心使用這個方案)。

GC日誌中To-space exhausted引起的異常暫停

絕大部分是由於大物件分配過多,GC日誌中頻繁出現

Pause Young (Concurrent Start) (G1 Humongous Allocation)

大物件分配過多,會導致堆空間快速被佔滿,GC是出現To-space exhausted/evacuation failure,需要額外的暫停時間處理,甚至出現更耗時的Full GC全堆整理。

GC過於頻繁

相比傳統的CMS/Parallel GC,固定的young 區size。G1的young區size是自動調整的,當爲了滿足暫停要求時,會縮小young區,導致GC頻率過高。一般的情況是避免MaxGCPauseMillis設定過小,參考上面引數的介紹。或者增大MaxGCPauseMillis的配置,同時有必要的話諮詢答疑專家,調整-XX:NewSize和-XX:MaxNewSize。

Mixed GC暫停過長

G1除了整理清除young區物件的young GC,還有在Concurrent mark之後,包含整理老區物件的mixed gc。因此通常mixed GC會有更長的暫停時間。如果單次mixed GC暫停過長,考慮增大上面介紹的引數G1MixedGCCountTarget,來進一步分散老區物件整理的任務,降低暫停

四、升級效果

日常執行

JDK8升級JDK11詳細說明

可以看到在日常執行中,G1的垃圾回收耗時也有不錯的提升

壓測效果

相同壓測條件下TPS20

JDK8升級JDK11詳細說明

JDK8升級JDK11詳細說明

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

JDK8升級JDK11詳細說明

線上執行情況

JDK8升級JDK11詳細說明

從圖中可以看到YGC的耗時明顯縮短,效能將近提升50%!這歸功於分代收集的能力


YGC平均暫停時間
YGC次數
效果
JDK8+CMS
7.4ms
10347

JDK11+G1
3.74ms
10649
效能提升49.5%

五、JDK11新玩法

字串String加強

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);        //複製幾遍字串
long  lineCount = str.lines().count();  //行數統計
System.out.println(isblank);            //結果:false            
System.out.println(isempty);            //結果:false 
System.out.println(result1);            //結果:i am lzc 
System.out.println(result2);            //結果: i am lzc 
System.out.println(result3);            //結果:i am lzc  
System.out.println(copyStr);            //結果: i am lzc  i am lzc  
System.out.println(lineCount);          //結果:1

檔案Files方法加強

Path filePath = Files.writeString(Path.of("/temp/a.txt"), "Sample text");
String fileContent = Files.readString(filePath);
System.out.println(fileContent.equals("Sample text"));

數據流Stream方法加強

//Stream,允許接受一個null值,計算count時,返回0
long count = Stream.ofNullable(null).count();
System.out.println(count); // 0
//方法都接受一個謂詞來決定從流中放棄哪些元素
//通俗理解:從集合中刪除滿足條件的元素,直到不滿足為止
List list1 = Stream.of(1, 2, 3, 2, 1)
        .dropWhile(n -> n < 3)
        .collect(Collectors.toList());
System.out.println(list1); // [3, 2, 1]
//方法都接受一個謂詞來決定從流中選用哪些元素
//通俗理解:從集合中提取滿足條件的元素,直到不滿足為止
List list2 = Stream.of(1, 2, 3, 2, 1)
        .takeWhile(n -> n < 3)
        .collect(Collectors.toList());
System.out.println(list2); // [1, 2]

集合List、Map等方法加強

List list1 = List.of(1, 3, 5, 7);
List list2 = List.copyOf(list1);
System.out.println(list2); //結果: [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); //結果: {1=a, 2=b, 3=c}

optional加強

//新增orElseThrow,為空時拋異常
Object v2 = Optional.ofNullable(null).orElseThrow();      //結果:拋異常
//新增ifPresentOrElse,不為null執行第1個回撥函式,為null時執行第2個回撥函式
Optional.ofNullable(null).ifPresentOrElse(
        (x) -> {
            System.out.println("資料:" + x);
        }, () -> {
            System.out.println("資料不存在");
        });
//提供另一個Optionals 作為空Optionals的回撥
Object v3 = Optional.ofNullable(null)
        .or(() -> Optional.of("fallback"))
        .get();   //結果: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());


0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.