JDK高版本特性总结与ZGC实践

B站影视 港台电影 2025-06-20 17:09 1

摘要:美团信息安全技术团队核心服务升级JDK 17后,性能与稳定性大幅提升,机器成本降低了10%。高版本JDK与ZGC技术令人惊艳,且Java AI SDK最低支持JDK 17。本文总结了JDK 17的主要特性,然后重点分享了JDK 17+ZGC在安全领域的一些实践

美团信息安全技术团队核心服务升级JDK 17后,性能与稳定性大幅提升,机器成本降低了10%。高版本JDK与ZGC技术令人惊艳,且Java AI SDK最低支持JDK 17。本文总结了JDK 17的主要特性,然后重点分享了JDK 17+ZGC在安全领域的一些实践,希望能对大家有所帮助或启发。

本文目录

1. JDK 17的主要特性1.1 语言特性[1]1.2 新API和工具1.3 性能优化与Bug修复2. JDK17+ZGC在安全领域的实践2.1 美团JDK的现状2.2 ZGC适用场景2.3 ZGC效果2.4 ZGC实现原理简介3. JDK17升级实践过程3.1 安装与兼容性问题3.2 性能压测3.3 JVM参数4. 总结

从一句调侃的话 “你发任你发,我用Java 8!” 可以看出,在开发新项目时,Java 8依然是大家的首选。美团Java 8服务占比超过70%,可以说java 8依然是绝对的主流。但是,我们在多个核心服务上遇到较多的性能问题,这些问题无法通过JVM参数微调来解决,为此我们对部分核心服务使用了 JDK 17,升级后服务性能和稳定性指标也得到巨大的飞跃,同时机器成本可以下降约10%,升级JDK版本收益十分明显。

另外,目前正处在AI时代的爆发期,Java AI SDK的最小支持版本为JDK 17,这让升级JDK版本变得更具价值。接下来,期望跟大家一起探索JDK高版本和ZGC技术的奥秘,开启优化Java应用的新征程。

| 包含JDK 9~17等中间版本的特性。

从 JDK 8 直接升级到 JDK 17,以下是需要重点关注的特性,这些特性对开发效率、代码风格、性能优化和安全性都有显著影响。

使用var关键字来声明局部变量,而无需显式指定变量的类型。在Java 17中,可以使用局部变量类型推断的扩展来编写更简洁的代码。其他语言如Golang很早就支持了var变量。

// JDK8String str = "Hello world";// JDK17var str = "Hello world";

| 需要注意的是,Var类型的局部变量仍然具有静态类型,一旦被推断出来,类型就会固定下来,并且不能重新赋值为不兼容的类型。

它允许我们将类或接口的继承限制为一组有限的子类。如果想将类或接口的继承限制为一组有限的子类时,这非常有用。在下面的示例中,可以看到我们如何使用sealed关键字将类的继承限制为一组有限的子类。我们可以通过在类的声明前加上sealed关键字来将该类声明为密封类。然后,可以使用permits关键字列出该密封类允许继承的子类。这些子类必须直接或间接地继承自密封类。这样,只有在这个预定义的子类中,才能继承该密封类。

//使用permits关键字列出了允许继承的子类Circle、Rectangle和Trianglepublic sealed class Shape permits Circle, Rectangle, Triangle { // 省略实现}// 在与密封类相同的模块或包中 定义以下三个允许的子类, Circle,Square和:Rectanglepublic final class Circle extends Shape { public float radius;} public non-sealed class Square extends Shape { public double side;} public sealed class Rectangle extends Shape permits FilledRectangle { public double length, width;}

Record 类的主要目的是提供一种更简洁、更安全的方式来定义不可变的数据载体类。它自动实现了常见的方法(如equals、hashCode、toString和构造函数),从而减少了样板代码。

特点

不可变性:Record类的字段默认是final的,因此 Record 类是不可变的。简洁性:Record类自动提供了构造函数、equals、hashCode和toString方法,无需手动编写。组件访问:Record类的字段可以通过recordName.fieldName的方式直接访问。模式匹配:Record类支持模式匹配(Pattern Matching),可以与instanceof和switch表达式结合使用。

Record类的定义非常简单,只需要使用record关键字,并声明字段类型和名称即可。例如:

// 这里有一个包含两个字段的记录类record Rectangle(double length, double width) { }// 这个简洁的矩形声明等同于以下普通类public final class Rectangle { private final double length; private final double width; public Rectangle(double length, double width) { this.length = length; this.width = width; } double length { return this.length; } double width { return this.width; } // ... public boolean equals... public int hashCode... // ... public String toString {...}}

在Java 17中使用switch表达式时,不必使用关键字break来跳出switch语句,或return在每个switch case上使用关键字来返回值;相反,我们可以返回整个switch表达式。这种增强的switch表达式使整体代码看起来更清晰,更易于阅读。switch打印一周中某一天的字母数量的语句。

JDK 8

public enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY; } // ... int numLetters = 0; Day day = Day.WEDNESDAY; switch (day) { case MONDAY: case FRIDAY: case SUNDAY: numLetters = 6; break; case TUESDAY: numLetters = 7; break; case THURSDAY: case SATURDAY: numLetters = 8; break; case WEDNESDAY: numLetters = 9; break; default: throw new IllegalStateException("Invalid day: " + day); } System.out.println(numLetters);

JDK 17

Day day = Day.WEDNESDAY; System.out.println( switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; default -> throw new IllegalStateException("Invalid day: " + day); } );

在不使用转义序列的情况下创建多行字符串。在创建SQL查询或JSON字符串时非常有用。在下面的示例中,可以看到使用文本块时代码看起来更加简洁。

// JDK8String message = "'The time has come,' the Walrus said,\n" + "'To talk of many things:\n" + "Of shoes -- and ships -- and sealing-wax --\n" + "Of cabbages -- and kings --\n" + "And why the sea is boiling hot --\n" + "And whether pigs have wings.'\n";// 使用文本块可以消除大部分混乱:String message = """ 'The time has come,' the Walrus said, 'To talk of many things: Of shoes -- and ships -- and sealing-wax -- Of cabbages -- and kings -- And why the sea is boiling hot -- And whether pigs have wings.' """;

SQL注解描述

// JDK8 @Select("select distinct ta.host_name from tb_agent_info tai, tb_agent ta where 1=1 " + "and ta.host_name=tai.host_name and ta.status=1 and ta.master=1 and tai.report_pid_count > 0")SetqueryAllJavaHost;// JDK17@Select(""" SELECT DISTINCT ta.host_name FROM tb_agent_info tai, tb_agent ta WHERE 1=1 AND ta.host_name = tai.host_name AND ta.status = 1 AND ta.master = 1 AND tai.report_pid_count > 0 """) SetqueryAllJavaHost2;可读性更强:文本结构清晰可见,无需处理转义字符或字符串连接。减少错误:不需要手动添加换行符(\n),降低了出错的可能性。易于编辑:可以直接复制粘贴格式化好的JSON,而不需要额外的处理。保留缩进:文本块会保留的缩进,使得其在Java代码中的呈现更加美观。

它允许将instanceof运算符用作返回已转换对象的表达式。当我们使用嵌套的if-else语句时,这非常有用。在下面的示例中,可以看到我们如何使用instanceof运算符来捕获对象,而不是进行显式转换。

JDK 8

Object obj = ...;if (obj instanceof String) { String str = (String) obj; int length = str.length; System.out.println("字符串长度:" + length);}

JDK 17

Object obj = ...;if (obj instanceof String str) { int length = str.length; System.out.println("字符串长度:" + length);}

对象空指针在日常开发中遇到的比较多,一般代码报错只能精确的某一行,如果该行的代码比较复杂,涉及到多个对象,往往不能直接确定是哪一个对象为空。

public class NpeDemo { public static void main(String args) { Address address=new Address; User user=new User; user.setAddress(address); log.info(user.getAddress.getCity.toLowerCase); }}

上面代码中的第6行链式调用,如果某一个环节出现空指针,将会抛出空指针的异常:

Exception in thread "main" java.lang.nullPointerException at NpeDemo.main(Main.java:6)

使用JDK 17

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.toLowerCase" because the return value of "Address.getCity" is null at NpeDemo.main(Main.java:6)

集合增强:不可变集合:引入了创建不可变集合的便捷方法,如List.of、Set.of和Map.of。这些方法用于快速创建不可变集合,减少了代码量并提高了安全性。

import java.util.*;public class CollectionsDemo { public static void main(String args) { // 创建不可变list Listlist = List.of("Java", "Golang", "Python"); // 创建不可变set Setset = Set.of("Java", "Golang", "Python"); // 创建不可变map Mapmap = Map.of("Java", 1, "Golang", 2, "Python", 3); }}

集合工厂方法:Java 17还引入了集合工厂方法,如List.copyOf、Set.copyOf 和 Map.copyOf,用于从现有集合创建不可变副本。

Stream API增强:takeWhile和dropWhile:基于条件截取或跳过元素;iterate:支持终止条件的迭代;ofNullable:将可能为null的值转换为Stream。

Optional增强:ifPresentOrElse:值存在时执行操作,否则执行另一个操作;or:在值不存在时提供替代值;stream:将Optional转换为Stream。

可以使用HttpClient使用来发送请求并检索其响应。HttpClient可以通过builder来创建。该newBuilder方法返回一个构建器,用于创建默认HttpClient实现的实例。该构建器可用于配置每个客户端的状态,例如:首选协议版本(HTTP/1.1 或 HTTP/2)、是否遵循重定向、代理、身份验证器等。构建完成后,HttpClient是不可变的,可用于发送多个请求。

// 同步示例HttpClient client = HttpClient.newBuilder .version(Version.HTTP_1_1) .followRedirects(Redirect.NORMAL) .connectTimeout(Duration.ofSeconds(20)) .proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80))) .authenticator(Authenticator.getDefault) .build; HttpResponseresponse = client.send(request, BodyHandlers.ofString); System.out.println(response.statusCode); System.out.println(response.body); // 异步示例HttpRequest request = HttpRequest.newBuilder .uri(URI.create("https://foo.com/")) .timeout(Duration.ofMinutes(2)) .header("Content-Type", "application/json") .POST(BodyPublishers.ofFile(Paths.get("file.json"))) .build; client.sendAsync(request, BodyHandlers.ofString) .thenApply(HttpResponse::body) .thenAccept(System.out::println);

如果不希望引入三方依赖(三方依赖漏洞和Bug等需要经常升级),可以使用JDK提供的原生的httpClient API,适用场景中间件。

该工具将以Java应用程序和Java运行时镜像作为输入,生成包含所有必要依赖项的Java应用程序镜像。它能够生成特定平台格式的原生软件包,例如Windows上的exe文件或macOS上的dmg 文件。每种格式都必须在其运行的平台上构建,不支持跨平台。该工具将提供一些选项,允许以各种方式定制打包的应用程序。该工具最大特点是无需单独安装JDK环境,例如用JDK17写了一个MCP Server工具,直接打包为可执行文件安装即可,减少环境依赖安装。

进程管理功能得到了显著增强,ProcessHandle提供了更强大的功能来创建、监控和管理本地进程。这些改进使得Java程序能够更灵活地与操作系统交互,同时提供了更详细的进程信息和更强大的生命周期管理功能。

1. 创建进程

在Java中,创建新进程通常使用ProcessBuilder或Runtime.getRuntime.exec。而Java 17上ProcessHandle提供了更强大的功能来管理这些进程。

ProcessBuilder pb = new ProcessBuilder("echo", "Hello World!");Process p = pb.start;

2. 监控进程

public class ProcessTest { // ... static public void startProcessesTest throws IOException, InterruptedException { Listgreps = new ArrayList; greps.add(new ProcessBuilder("/bin/sh", "-c", "grep -c \"java\" *")); greps.add(new ProcessBuilder("/bin/sh", "-c", "grep -c \"Process\" *")); greps.add(new ProcessBuilder("/bin/sh", "-c", "grep -c \"onExit\" *")); ProcessTest.startSeveralProcesses (greps, ProcessTest::printGrepResults); System.out.println("\nPress enter to continue ...\n"); System.in.read; } static void startSeveralProcesses ( ListonExitMethod) throws InterruptedException { System.out.println("Number of processes: " + pBList.size); pBList.stream.forEach( pb -> { try { Process p = pb.start; System.out.printf("Start %d, %s%n", p.pid, p.info.commandLine.orElse("")); p.onExit.thenAccept(onExitMethod); } catch (IOException e) { System.err.println("Exception caught"); e.printStackTrace; } } ); } static void printGrepResults(Process p) { System.out.printf("Exit %d, status %d%n%s%n%n", p.pid, p.exitValue, output(p.getInputStream)); } private static String output(InputStream inputStream) { String s = ""; try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) { s = br.lines.collect(Collectors.joining(System.getProperty("line.separator"))); } catch (IOException e) { System.err.println("Caught IOException"); e.printStackTrace; } return s; } // ...}

3. 获取进程信息

public static void getInfoTest throws IOException { ProcessBuilder pb = new ProcessBuilder("echo", "Hello World!"); String na = ""; Process p = pb.start; ProcessHandle.Info info = p.info; System.out.printf("Process ID: %s%n", p.pid); System.out.printf("Command name: %s%n", info.command.orElse(na)); System.out.printf("Command line: %s%n", info.commandLine.orElse(na)); System.out.printf("Start time: %s%n", info.startInstant.map((Instant i) -> i .atZone(ZoneId.systemDefault).toLocalDateTime.toString) .orElse(na)); System.out.printf("Arguments: %s%n", info.arguments.map( (String a) -> Stream.of(a).collect(Collectors.joining(" "))) .orElse(na)); System.out.printf("User: %s%n", info.user.orElse(na));}输出Process ID: 18761Command name: /usr/bin/echoCommand line: echo Hello World!Start time: 2017-05-30T18:52:15.577Arguments:

最近火热的AI大模型工具,JDK 8不再兼容,运行的最低版本为JDK 17,例如Spring AI工具。

ZGC作为新一代的垃圾回收器,主要目标:

支持TB级内存停顿时间控制在10ms之内对程序吞吐量影响小于15%

据官方测评数据,在内存为128GB的机器上,相比于G1来说,性能提高30%,停顿时间减少99%。

JVM的模块化是Java 9引入的一个重要特性,通过Java Platform Module System (JPMS) 实现。这一特性旨在解决Java应用在可扩展性和维护上的问题,提供更高级别的封装和依赖管理机制。

减少环境资源开销:在JDK 9之前,每次启动JVM都要耗费至少30MB到60MB的内存空间,因为JVM需要加载整个rt.jar。模块化允许JVM选择性地加载必需的模块,从而减少内存占用。提升开发效率和运行速度:随着代码库的复杂性增加,开发效率和运行速度会受到影响。模块化通过规范化路径和依赖关系,使系统更安全、更高效。规范化路径及依赖关系:JDK 9之前,系统没有对不同JAR之间的依赖或敏感路径进行限制,导致所有JAR都可以被访问,暴露了安全问题。模块化通过管理模块间的依赖关系,隐藏不必要的模块,提高了安全性和空间利用率。

Java Attach Socket文件被删除后会导致Java Agent注入失败,在JDK 8上只能通过重启解决,而JDK 17会重新创建一个新的文件。

更及时地将未使用的元空间内存回收,减少元空间占用的内存。

在美团信息安全部,JDK8(Oracle JDK8u201)依然是主流版本,其次是Open JDK17,剩下为Open JDK 11。

服务器成本压力大:服务器数量大于100台、单机配置大于16C16G、Java堆内存超过16G等。单机CPU高:峰值大约在50%性能火焰图中GC占比高高峰期故障雷达、监控大盘和服务日志等告警频繁

在测试服务不同接口中,ZGC在高QPS场景中收益较大(服务的QPS超过1万):

TP9999:下降220~380ms,下降幅度18%~74%。TP999:下降60-125ms,下降幅度10%~63%。TP99:下降 3ms-20ms,下降幅度0%-25%。

一些重度依赖外部的接口中性能优化不大,原因是这些服务的响应时间瓶颈不是GC,而是外部依赖的性能,在一些低QPS接口中对比不太明显。

峰值cpu.busy指标下降

升级前: 47.8565%

升级后: 41.4933%

系统长期运行时TP9999性能稳定

运行15天,JDK11机器长时间不重启三九、四九线会逐渐升高,JDK 17机器较为稳定。

服务失败率显著降低

UGC集群升级效果:错误数量由峰值6000下降到349。

JVM元空间使用降低

单机维度高峰期性能指标

该服务是内容安全的代理层,主要负责匹配请求的分发、辅助功能支撑(日志、监控、熔断)以及一些个性化业务需求。当前该服务GC是CMS,该服务线上的Young GC平均耗时是17ms,平均每分钟GC次数是6次,该服务接口平均响应时间是2.6ms。

根据文章《从实际案例聊聊Java应用的GC优化》中提供的计算方式,受到Young GC影响的请求占比是:

即有0.196%的请求收到GC时间0-17ms不等的影响。其中收到GC停顿完整影响的请求占比:

即其中有0.026%的请求受到完整的GC停顿时间影响,即耗时增加17ms,可以大致理解为请求响应的9999线会因GC停顿而导致17ms的上涨。

根据ZGC的STW的耗时在毫秒甚至亚毫秒级别,因此理论上升级后服务的9999线可以降低17ms左右。在实际生产中,还会有Full GC的影响,会带来耗时的进一步提升,ZGC在该部分可以避免Full GC带来的影响。

服务升级采用的是Tomcat 9+JDK 17的配置,录制线上流量进行压测,使用同样的流量对先前采用CMS垃圾回收的以及采用ZGC垃圾回收方式的同时进行压测。服务器配置均为8C16G,800QPS的压测,通过2h左右的压测。

分析接口耗时统计:可得到以下数据,发现耗时均有明显下降,9999线的下降量低于理论的17ms,由于实际环境中其他因素的影响也基本符合预期。

分析CPU和JVM占用情况:CPU和JVM占用情况发现,CPU占用在峰值处会提升10%左右,JVM占用情况基本一致。

更多详情,可参考《新一代垃圾回收器ZGC的探索与实践》一文。

在介绍ZGC之前,首先回顾一下CMS和G1的GC过程以及停顿时间的瓶颈。CMS新生代的Young GC、G1和ZGC都基于标记-复制算法,但算法具体实现的不同就导致了巨大的性能差异。

标记-复制算法应用在CMS新生代(ParNew是CMS默认的新生代垃圾回收器)和G1垃圾回收器中。标记-复制算法可以分为三个阶段:

标记阶段,即从GC Roots集合开始,标记活跃对象;转移阶段,即把活跃对象复制到新的内存地址上;重定位阶段,因为转移导致对象的地址发生了变化,在重定位阶段,所有指向对象旧地址的指针都要调整到对象新的地址上。

下面以G1为例,通过G1中标记-复制算法过程(G1的Young GC和Mixed GC均采用该算法),分析G1停顿耗时的主要瓶颈。G1垃圾回收周期如下图所示:

G1的混合回收过程可以分为标记阶段、清理阶段和复制阶段:

标记阶段停顿分析

初始标记阶段:初始标记阶段是指从根节点(GC Roots)出发标记全部直接子节点的过程,该阶段是STW的。由于GC Roots数量不多,通常该阶段耗时非常短。并发标记阶段:并发标记阶段是指从GC Roots开始对堆中对象进行可达性分析,找出存活对象。该阶段是并发的,即应用线程和GC线程可以同时活动。并发标记耗时相对长很多,但因为不是STW,所以我们不太关心该阶段耗时的长短。再标记阶段:重新标记那些在并发标记阶段发生变化的对象。该阶段是STW的。

清理阶段停顿分析

清理阶段清点出有存活对象的分区和没有存活对象的分区,该阶段不会清理垃圾对象,也不会执行存活对象的复制。该阶段是STW的。

复制阶段停顿分析

复制算法中的转移阶段需要分配新内存和复制对象的成员变量。转移阶段是STW的,其中内存分配通常耗时非常短,但对象成员变量的复制耗时有可能较长,这是因为复制耗时与存活对象数量与对象复杂度成正比。对象越复杂,复制耗时越长。

四个STW过程中,初始标记因为只标记GC Roots,耗时较短。再标记因为对象数少,耗时也较短。清理阶段因为内存分区数量少,耗时也较短。转移阶段要处理所有存活的对象,耗时会较长。因此,G1停顿时间的瓶颈主要是标记-复制中的转移阶段STW。为什么转移阶段不能和标记阶段一样并发执行呢?主要是G1未能解决转移过程中准确定位对象地址的问题。

与CMS中的ParNew和G1类似,ZGC也采用标记-复制算法,不过ZGC对该算法做了重大改进:ZGC在标记、转移和重定位阶段几乎都是并发的,这是ZGC实现停顿时间小于10ms目标的最关键原因。

ZGC垃圾回收周期如下图所示:

ZGC只有三个STW阶段:初始标记,再标记,初始转移。其中,初始标记和初始转移分别都只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,一般情况耗时非常短;再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。即,ZGC几乎所有暂停都只依赖于GC Roots集合大小,停顿时间不会随着堆的大小或者活跃对象的大小而增加。与ZGC对比,G1的转移阶段完全STW的,且停顿时间随存活对象的大小增加而增加。

单代:ZGC没有分代,基于“大部分对象朝生夕死”的假设,没有Young GC的概念(这里仅指JDK 17,JDK 21支持分代回收,性能更高)。基于Region:G1的每个Region大小是完全一样的,而ZGC的Region更灵活,其中大型Region大小不固定,可以动态变化,也不会被重分配,因为复制一个大对象代价太高。部分压缩:基于Region,“标记-整理”,相对CMS压缩时间更短。支持NUMA:对应有UMA,每个CPU对应有一块内存,每个CPU优先访问这块内存。染色指针

以前的垃圾回收器的GC信息都保存在对象头中,ZGC将GC 信息保存在了染色指针上,无需进行对象访问就可以获得GC 信息。这就是ZGC在标记和转移阶段速度更快的原因。Marked0、Marked1和Remapped这三个虚拟内存作为ZGC的三个视图空间,在同一个时间点内只能有一个有效。ZGC就是通过这三个视图空间的切换,来完成并发的垃圾回收。

读屏障

读屏障,在标记和移动对象的阶段,每次从堆里对象的引用类型中读取一个指针的时候,都需要加上一个Load Barriers。用于确定对象的引用地址是否满足条件,并作出相应动作。

主要分为三个阶段:安装部署、解决兼容性问题、性能测试与参数优化。

| 如果公司的中间件大部分基于JDK 8,工程代码编译可以基于JDK 8,运行环境使用JDK 17。

1. 主要的问题举例

JVM运行的报错信息:module java.base does not "opens java.util.concurrent.locks" to unnamed module。

[ERROR] main JsonUtil Json parse failedjava.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock java.util.concurrent.locks.ReentrantReadWriteLock.readerLock accessible: module java.base does not "opens java.util.concurrent.locks" to unnamed module @1ba9117e at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354) at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297) at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178) at java.base/java.lang.reflect.Field.setAccessible(Field.java:172) at com.fasterxml.jackson.databind.util.ClassUtil.checkAndFixAccess(ClassUtil.java:939) at com.fasterxml.jackson.databind.deser.impl.FieldProperty.fixAccess(FieldProperty.java:104)

2. 原因:JDK9之后Java API使用了模块化设计方案,用户模块无法反射调用Java代码,需要使用开启对应模块访问权限(没有引入新的安全问题,相当于没有用模块隔离的功能)。

3. 解决方式:JVM参数增加如下:

--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.base/java.time=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/jdk.internal.access=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED

其他软件等兼容性问题,根据自身服务报错,对应解决问题。

-Xmx18g -Xms18g 堆大小-XX:MaxDirectMemorySize=2G 直接内存-XX:+HeapDumpOnOutOfMemoryError 当JVM发生OOM时,自动生成DUMP文件。-XX:ReservedCodeCacheSize=256m -XX:InitialCodeCacheSize=256m 设置codecache大小 默认128m-XX:+UseZGC 使用ZGC-XX:ZAllocationSpikeTolerance=2 ZGC触发自适应算法的修正系数,默认2,数值越大,越早的触发ZGC-XX:ZCollectionInterval=0 ZGC的周期。默认值为0,表示不需要触发垃圾回收。固定周期垃圾回收。ZGC发生的最小时间间隔,单位秒-XX:ConcGCThreads=4 并发阶段的GC线程数,默认是总核数的12.5%-XX:ZStatisticsInterval=10 控制统计信息输出的间隔,默认10s-XX:ParallelGCThreads=16 并行工作线程数据,STW阶段使用线程数,默认是总核数的60%-Xlog:safepoint,classhisto*=trace,age*,gc*=info:file=/opt/logs/logs/gc-%t.log:time,tid,tags:filecount=5,filesize=50m' 设置GC日志中的内容、格式、位置以及每个日志的大小

本服务prod机器16c,16g成功运行起来的JVM参数(还在调整中,仅供参考):

-server -Xmx12g -Xms12g -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+UseZGC -XX:+UseDynamicNumberOfGCThreads -XX:ConcGCThreads=3 -XX:ParallelGCThreads=8 -XX:ZCollectionInterval=130 -XX:ZAllocationSpikeTolerance=1 -XX:MaxDirectMemorySize=460m -XX:MetaspaceSize=330m -XX:MaxMetaspaceSize=330m -XX:ReservedCodeCacheSize=256m -XX:InitialCodeCacheSize=256m -XX:+UseCountedLoopSafepoints -XX:+SafepointTimeout -XX:SafepointTimeoutDelay=500 -XX:GuaranteedSafepointInterval=0 -XX:+DisableExplicitGC -XX:+HeapDumpOnOutOfMemoryError -XX:ZStatisticsInterval=130 -XX:+PrintGCDetails -Xlog:safepoint,class+load=info,class+unload=info,classhisto*=trace,age*,gc*=info:file=/opt/logs/logs/gc-%t.log:time,tid,tags:filecount=5,filesize=50m --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.base/java.time=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/jdk.internal.access=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/sun.reflect.generics.reflectiveObjects=ALL-UNNAMED --add-opens java.base/jdk.internal.perf=ALL-UNNAMED --add-opens java.base/java.instrument=ALL-UNNAMED --add-opens jdk.attach/sun.tools.attach=ALL-UNNAMED

来源:小夭看天下

相关推荐