这篇文章主要介绍“java 中双 12 压测引出的线上 Full GC 怎么排查”,在日常操作中,相信很多人在 java 中双 12 压测引出的线上 Full GC 怎么排查问题上存在疑惑,丸趣 TV 小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”java 中双 12 压测引出的线上 Full GC 怎么排查”的疑惑有所帮助!接下来,请跟着丸趣 TV 小编一起来学习吧!
线上问题
双 12 之前压测的时候起了很小的量,直接触发了 Full GC,吓尿了,因为马上双 12 大促预热就要开始了,这搞不好妥妥的 3.25 啦。
赶紧拉群,把相关同学拉在一起排查问题。
第一时间查看 GC 日志:
可以看到原因是超过了 Metadata GC 的阈值,触发了 Full GC,Metaspace 从 243M 回收到 231M,基本没怎么回收掉,所以稍微再来点量,很容易再次触发 Metaspace 的回收。
知识储备
GC 问题排查需要很多储备知识,最主要是 JVM 相关的,之前文章已经讲过一些了,这里主要讲 Matespace 是什么?后面讲怎么做的 GC 问题的排查。
这里有二个知识点:
Matespace(元空间)是什么?在 JVM 中扮演什么角色,也就是存放什么的?Full GC 跟 Matespace 大小设置有什么关系?
Matespace 叫做元空间,从 JDK 8 开始,永久代 (PermGen) 的概念被废弃掉了,取而代之的是一个称为 Metaspace 的存储空间。
Metaspace 用来存放:Class 文件在 JVM 里的运行时数据结构;以及存 Klass 相关的其他的内容,比如 Method,ConstantPool 等。
Metaspace 使用的是本地内存,而不是堆内存,也就是说在默认情况下 Metaspace 的大小只与本地内存大小有关。但是很容易有个误区是 Matespace 可以随便用,不管使用多少,只要不超本地内存就不会触发 GC,这是错误的。
Matespace 的设置方式是:-XX: MetaspaceSize=**M, 这个 JVM 参数的作用是让 Matespace 达到 MetaspaceSize 时触发 Full GC, 如果不设置 Matespace, 默认值很小,也就 20M 左右(不同系统会有一点差别),如果程序 Load Class 比较多,很容易触发 Full GC。这里要明白的是 Class 信息和加载 Class 的 ClassLoader 都存放在 Metaspace,我们知道一个类是由这个类的类加载器加上全限定名(由包名 类名组成)确定唯一性的。
所以大家可以检查一下自己应用 JVM Metaspace 设置的大小,如果没设置可以通过 -XX:+PrintFlagsInitial 查看一下默认值。
(之前文章发过 GC 日志的详细讲解以及 JVM 参数的配置说明,如果有疑问的同学可以去看看历史文章。)
问题排查
刚开始看到 Full GC 频繁,查看日志是由于 Metaspace 空间不够造成的,第一反应是调整 Metaspace 大小,把 MetaspaceSize 从 256M 提高到了 512M。但是发现 Metaspace 引发的 Full GC 还是没有消除。
立即 dump 了二台机器的日志,第一次分析 GC 日志文件,没发现异常,这里有个注意的地方,大家 dump 文件时机很重要,有时候 dump 的 GC 日志没问题是因为刚好 Full GC 完成之后 dump 的,内存回收的干干净净,有些内存缓慢增加的问题一定要在 Full GC 前 dump。
期间我们还发现缓存相关的对象占用内存较高,但是经过分析,缓存对象生命周期本身就比较长,所以常驻在堆上,没有问题,继续看。
排查发现 Metaspace 内存占用是随着双 12 新接口压测流量的增长而增长,所以可以确定是新接口代码引入。
分析 GC dump 日志发现可疑点,同一个 ClassCloader 加载了接近 3000 个,如下图所示,前面我们说过,ClassCloader 信息在 Metaspace 区域。
破案了,fastjson 使用不当引入了 ASM Serializer 的坑。
故障定位 修复
FastJson 之所以快,原因就是使用 asm 字节码增强替代了反射。所以肯定是代码中应用了 fastjson 的 ASM 处理数据时造成的频繁加载同一个类,基本问题清楚了,那就是撸代码了,很快就定位了问题代码。
因为保密原因,不方便放原始代码,安琪拉撸了个类似逻辑的代码:
for(Item item - arrays) {
SerializeConfig serializeConfig = new SerializeConfig();
serializeConfig.put(Custom.class, new CustomSerializer());
jsonString = JSON.toJSONString(item, serializeConfig);
}
这段代码是自定义了一个序列化实现类 CustomSerializer,在进行序列化时进行了一个类似 AOP 的切面处理,对所有 Custom 类型的字段进行统一自定义序列化(使用 CustomSerializer)。
实现原理上是对需要序列化的 Class 使用 asm 动态生成了代理类,在这里就是 Item 类,使用 SerializeConfig 配置动态生成字节码代理类: com.alibaba.fastjson.serializer.ASMSerializer_1_Item,但是每次 new SerializeConfig 对象,会当作每次代理的目标是不一样的,导致每次会使用一个新的类加载器 ASMClassLoader,所以 Metaspace 很快就满了,触发了频繁 Full GC。
如果希望深入研究可以看下 FastJson 源码:
com.alibaba.fastjson.serializer.SerializeConfig#createASMSerializer
问题修复:
注册 ObjectSerializer,让 ObjectSerializer 成为全局唯一的,相当于是单例的。
SerializeConfig.getGlobalInstance().put(Character.class, new CharacterSerializer());
SerializeConfig 改成单例的后,每次序列化不用重复加载。
类似问题排查 调优如果 dump 日志发现很多 classloader 名称前缀相同,排查是否有这种动态代理技术的使用,可能在不断生成代理对象。发现内存缓慢增长,GC 回收不掉,dump GC 日志,查看是否有类被重复加载;Metaspace 调优,比如我们现在生产环境 Metaspace 基本会设置 256M 或者 512M,可以根据应用的类型和机器内存配置来决定,因素:1. 是否会加载比较多的类,2. 机器内存允许, 可以适当调大 Metaspace。
到此,关于“java 中双 12 压测引出的线上 Full GC 怎么排查”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注丸趣 TV 网站,丸趣 TV 小编会继续努力为大家带来更多实用的文章!