开发者

jdk8升级到jdk11如何升级的真实案例(亲身经历)

目录
  • 一、背景
    • 为什么要升级JDK11
  • 二、升级前你要知道的点
    • 三、升级过程
      • 修改pom文件
      • jdk11已移除,需手工依赖二方库
      • 应用不能进行远程调试
      • 其他编程客栈问题
      • 应用部署发布
        • -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暂停过长
    • 四、升级效果
      • 五、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></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&编程客栈lt;/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: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"
            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 swhRQyTize/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);            //结果:jsi 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());

            六、常见问题解决‌

            • JVM参数调整‌:移除-Xverify等废弃参数,替换为--add-opens解决模块访问限制。
            • 类路径冲突‌:JDK 11默认不包含javax.xml等包,需显式添加依赖(如--add-modules=jdk.xml.dom)。

            七、总结

             升级后需全面回归测试,建议先在非生产环境验证。建议使用jdeprscan‌:检测废弃API。

            到此这篇关于jdk8升级到jdk11如何升级的真实案例(亲身经历)的文章就介绍到这了,更多相关jdk8升级到jdk11内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

            0

            上一篇:

            下一篇:

            精彩评论

            暂无评论...
            验证码 换一张
            取 消

            最新开发

            开发排行榜