原文标题:一文帮你搞定JDK8升级11
原文作者:阿里云开发者
冷月清谈:
升级要点: * JDK11改动较大且不兼容,升级难度取决于系统复杂度。 * 需注意二方包支持、API变动和验证的重要性。
升级过程: * 本地JDK11安装和配置。 * 框架升级: * Spring Boot,修改pom文件中的版本信息。 * Spring,框架版本至少为5.1.x。 * Netty,升级到4.1.33.Final或以上。 * 修改pom文件中的依赖,添加必要项。(可参考文章中的代码示例)
升级效果: * G1垃圾回收时耗明显降低,日常运行和压测性能大幅提升。 * YGC次数减少近50%,平均暂停时间缩短49.5%。
JDK11新玩法: * 字符串增强:isBlank()、isEmpty()、strip()等方法。 * 文件操作增强:WriteString()、readString()等方法。 * 数据流增强:dropWhile()、takeWhile()等方法。 * 集合增强:List.of()、Map.of()等方法。 * Optional增强:orElseThrow()、ifPresentOrElse()等方法。 * HTTP Client:简化HTTP请求发送和接收。
怜星夜思:
2、升级G1 GC后,GC日志中出现To-space exhausted异常,如何解决?
3、在升级JDK11后,如何理解GC频率逐渐提升的问题?
4、升级后,为什么会出现线上运行时容器内存占用反而增加的情况?
5、JDK11中有哪些新的HTTP Client功能?
6、对于处于业务稳定期但在JDK8上的系统,是否还有必要升级JDK11?
原文内容
阿里妹导读
一、背景
为什么要升级JDK11
-
性能
-
JDK11的G1的GC性能高很多,对比JDK8无论是性能还是内存占比都有很大的提升,业内各项数据指标也都表明JDK11的G1在应对突发流量的下的效果惊人;
-
版本兼容
-
Spring Boot 2.7.x及以后的版本将不再支持Java 8作为最低版本。Spring Boot 2.6.x是最后一个正式支持Java 8的主线版本,一些新的中间件与组件也不再支持JDK8了;
-
必然趋势
-
JDK11(LTS)已经成为业界主流,在Java开发社区和工业界中得到了广泛的接受和使用;
二、升级前你要知道的点
-
JDK11版本改动较大,且不会向下兼容。所以当你的业务代码越复杂,调用的链路越多,升级的难度越大。你会遇到很多兼容性问题,比如 二方包不支持新版本JDK;
-
JDK11移除了部分在Java 8就已经标记为过时的API例如sun.misc.Unsafe的部分方法,所以你的升级可能还涉及到代码的改动;
-
验证是个漫长而又耗时的过程,很多问题可能在运行时阶段才会暴露,你需要验证系统整体功能来保证系统稳定;
三、升级过程
本地升级,让你的JDK11跑起来
-
本地JDK11下载
-
IDEA选择JDK11启动
-
框架升级
<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>
-
遇到的问题
需要指定版本号
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>6.2.4.Final</version> </dependency>
应用部署发布
-
使用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团队)
-Xmn参数一般不需要设置
-XX:NewRatio同理不需要设置
-XX:SurvivorRatio一般也不设置
升级G1后可能需要关注的参数
-XX:MaxGCPauseMillis=N,G1暂停的目标时间(毫秒)
-XX:InitiatingHeapOccupancyPercent=N -XX:-G1UseAdaptiveIHOP (电商核心使用)
-XX:G1HeapRegionSize,(电商核心通常使用8m-32m)
-XX:G1HeapWastePercent,(默认5,部分电商核心应用设置为2)
-XX:G1MixedGCCountTarget,Mixed GC目标次数,默认为8
升级G1的常见问题
CMS升级G1后,容器和Java进程内存占用变高
GC日志中To-space exhausted引起的异常暂停
GC过于频繁
Mixed GC暂停过长
四、升级效果
日常运行
压测效果
可以明显看到GC耗时降低了不少,速度快了70%左右
线上运行情况
从图中可以看到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());