今年天猫国际技术团队计划从4月份开始先升级并应用JDK11。升级后台系统时一切比较顺利,但升级核心应用时出现了两个意外情况,也是比较典型的问题,这里记录一下。
问题一
在4月份的非核心应用升级过程中出现的问题有一定的沉淀,此次核心应用升级除了包版本冲突等常规问题,升级过程中一切顺利,预发环境也顺利部署完毕。预发部署一段时间后问题注入。问题表现为预发机器重启完毕后,过一段时间就会出现服务不响应,cpu利用率打满情况。
▐ 排查过程
最初怀疑是内存泄露导致,dump内存后没有发现泄露(时间太久没有当时相关记录了)但是top命令查看进程的资源占用时,全部都是GC进程,将线程号从十进制转成十六进制查询也未获取到有效信息。
进一步排查,发现jdk升级之后metaspace利用率升高5%,是不是和这个相关呢?
咨询了jvm同学后得到了准确的答复是升级jdk的向导默认是会把metaspace的参数加上,我这里是因为metaspace占用率太高,fullgc时metaspace被占满了导致循环full gc。
▐ 原因分析
那么为什么jdk升级会出现metaspace升高呢?
在Java 8中,方法区(Method Area)是用来存放类的元数据的,JVM使用PermGen(永久代)来管理这部分内存。在Java 8及之前的版本中,PermGen的大小是固定的,这样会导致如果类加载较多,可能会导致OutOfMemoryError: PermGen space的问题。
Java 8中引入了Metaspace,取代了PermGen。Metaspace的一个关键特点是它是基于本地内存的,而不是JVM的堆内存。这意味着Metaspace的大小不再受到固定大小的限制,而是可以根据需要动态扩展。Metaspace可以根据应用的需要动态增加或回收,因此在类加载和卸载的过程中,Metaspace的利用率会有较大的变化。
将JDK升级之后,Metaspace的利用率上升可能有以下几个原因:
1、类加载和卸载机制:在Metaspace中,类的元数据会在加载时分配空间,而在类卸载时,相关空间会被回收。如果应用程序在运行过程中频繁加载新的类但不卸载旧的类,那么Metaspace的利用率会增加。
2、JVM的参数设置:Metaspace有几个与其大小相关的JVM参数,比如:
-XX:MetaspaceSize:初始分配的Metaspace大小。
-XX:MaxMetaspaceSize:Metaspace的最大大小(如果不设置,默认为无穷大)。根据这些参数的不同配置,可能会影响Metaspace的利用情况。
3、新特性和依赖库:在升级到JDK 11的过程中,可能引入了一些新的库或依赖,导致额外的类被加载,或者使用了更多的反射和动态代理等特性,这会增加类的数量,从而提高Metaspace的利用率。
4、性能优化和调试:JDK 11相对于JDK 8有许多性能优化和改进,同时也可能引入了一些新的调试或监控工具,这些工具本身也可能会需要额外的类和元数据,造成Metaspace的利用率上升。
综上所述,我升级的应用除JVM参数设置因素,其他几个因素都有可能命中。
▐ 问题解决
既然升级jdk所需内存容量不足,那就简单的扩容一下就行。将metaspace加大到了768m后,metaspace利用率从95%降至80%循环fullgc问题得到恢复。
问题二
JDK升级发布之后,出现了另外一个问题,发布后内存利用率逐步提升,从而导致过一段时间会出现内存利用率高告警。
▐ 排查过程
经过多方面查询最终定位排查发现升级之后堆外内存竟然增加了500M(查看历史堆外内存记录)
▐ 原因分析
技术社区搜索JDK升级各JDK版本变动内容相关信息,将信息与问题整合得到的信息是堆外内存升高的根因是以下netty源码导致。
if (maxDirectMemory == 0 /* hasUnsafe 是否可以使用Unsafe类 默认false */|| !hasUnsafe() /* 关键点:hasDirectBufferNoCleanerConstructor默认false DirectByteBuffer的构造器是否允许反射调用 */|| !PlatformDependent0.hasDirectBufferNoCleanerConstructor() ) {USE_DIRECT_BUFFER_NO_CLEANER = false;
} else {USE_DIRECT_BUFFER_NO_CLEANER = true;if (maxDirectMemory < 0) {maxDirectMemory = MAX_DIRECT_MEMORY;if (maxDirectMemory <= 0) {DIRECT_MEMORY_COUNTER = null;} else {DIRECT_MEMORY_COUNTER = new AtomicLong();}} else {DIRECT_MEMORY_COUNTER = new AtomicLong();}
}
访问权限不受限时(JDK8中, 也就是else分支)设置了
USE_DIRECT_BUFFER_NO_CLEANER 关键代码和介绍:
/*** Allocate a new {@link ByteBuffer} with the given {@code capacity}. {@link ByteBuffer}s allocated with* this method <strong>MUST</strong> be deallocated via {@link #freeDirectNoCleaner(ByteBuffer)}.*/
public static ByteBuffer allocateDirectNoCleaner(int capacity) {assert USE_DIRECT_BUFFER_NO_CLEANER;incrementMemoryCounter(capacity);try {return PlatformDependent0.allocateDirectNoCleaner(capacity);} catch (Throwable e) {decrementMemoryCounter(capacity);throwException(e);return null;}
}
上述代码申请内存前通过全局内存计数器DIRECT_MEMORY_COUNTER,在每次申请内存的时候调用incrementMemoryCounter 增加相关的size,如果达到DIRECT_MEMORY_LIMIT(默认是-XX:MaxDirectMemorySize) 则直接抛出异常。PlatformDependent.allocateDirectNoCleaner是通过Unsafe去申请内存,再用构造器DIRECT_BUFFER_CONSTRUCTOR通过内存地址和大小来构造DirectBuffer。释放也可以通过Unsafe.freeMemory根据内存地址来释放相关内存。
得到结论:JDK8堆外的申请和使用都是netty自己管理的。
而在JDK 11中运行到if的分支(原因在于JDK9开始增加的模块化与访问权限加强导致在不配置--add-opens的情况下,未开放的类是不允许反射访问的)。导致Netty原生的内存分配机制无法生效,只能使用java.nio包下的内存分配,而nio的内存分配机制会在应用流量达到最大堆外内存时,触发FullGC等待内存释放阻塞应用。
但是为什么没有fullgc 而是内存一直升高呢?
应用没有触发fullgc的原因,一个是因为内存没有触发到阈值,第二个是我们设置了-XX:+ExplicitGCInvokesConcurrent
启用此参数后,当应用程序调用 System.gc() 进行显式垃圾回收时,CMS 会并发执行回收操作(而不是完全停止应用线程进行回收)。CMS 是并发垃圾回收器,此参数确保即使显式调用垃圾回收,也会采用并发方式来避免长时间的应用暂停。
▐ 问题解决
知道为什么之后,问题就很好解决了,只要在Java启动参数中增加如下部分保持和Java8一样:
#通过设置以下参数可以让netty在JDK11和JDK8中一样自行管理内存
SERVICE_OPTS="${SERVICE_OPTS} -Dio.netty.tryReflectionSetAccessible=true"
#参数 打开Unsafe权限
SERVICE_OPTS="${SERVICE_OPTS} --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED"
#打开nio的包访问限制
SERVICE_OPTS="${SERVICE_OPTS} --add-opens=java.base/java.nio=ALL-UNNAMED"
添加上述启动参数后,重新打镜像重启后,推外内存显示恢复。内存使用率告警问题得到解决。
结语
本次JDK 11升级过程中遇到的内存问题,不仅反映了JDK版本升级带来的技术挑战,也展示了通过合理的参数调整和深入的技术分析,可以有效应对这些问题。未来在进行类似的升级时,建议提前做好充分的测试和性能评估,确保系统稳定运行。
团队介绍
天猫国际是中国领先的进口电商平台,也是阿里巴巴-淘天集团电商技术体系中链路最完整且最为复杂的技术产品之一,也是淘天集团拥有最完整业务形态的业务。在这里我们参与到阿里电商体系的绝大部分核心系统,同时借助区块链、大数据、AI算法等前沿技术助力业务高速增长。作为贴近业务前沿的技术团队,我们对于电商行业特性、跨境市场研究、未来交易趋势以及未来技术布局等都有着深度的理解。
¤ 拓展阅读 ¤
3DXR技术 | 终端技术 | 音视频技术
服务端技术 | 技术质量 | 数据算法