java中双12压测引出的线上Full GC怎么排查

13次阅读
没有评论

这篇文章主要介绍“java 中双 12 压测引出的线上 Full GC 怎么排查”,在日常操作中,相信很多人在 java 中双 12 压测引出的线上 Full GC 怎么排查问题上存在疑惑,丸趣 TV 小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”java 中双 12 压测引出的线上 Full GC 怎么排查”的疑惑有所帮助!接下来,请跟着丸趣 TV 小编一起来学习吧!

线上问题

双 12 之前压测的时候起了很小的量,直接触发了 Full GC,吓尿了,因为马上双 12 大促预热就要开始了,这搞不好妥妥的 3.25 啦。

java 中双 12 压测引出的线上 Full GC 怎么排查

赶紧拉群,把相关同学拉在一起排查问题。

java 中双 12 压测引出的线上 Full GC 怎么排查

第一时间查看 GC 日志:

java 中双 12 压测引出的线上 Full GC 怎么排查

可以看到原因是超过了 Metadata GC 的阈值,触发了 Full GC,Metaspace 从 243M 回收到 231M,基本没怎么回收掉,所以稍微再来点量,很容易再次触发 Metaspace 的回收。

  知识储备

GC 问题排查需要很多储备知识,最主要是 JVM 相关的,之前文章已经讲过一些了,这里主要讲 Matespace 是什么?后面讲怎么做的 GC 问题的排查。

java 中双 12 压测引出的线上 Full 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 还是没有消除。

java 中双 12 压测引出的线上 Full GC 怎么排查

立即 dump 了二台机器的日志,第一次分析 GC 日志文件,没发现异常,这里有个注意的地方,大家 dump 文件时机很重要,有时候 dump 的 GC 日志没问题是因为刚好 Full GC 完成之后 dump 的,内存回收的干干净净,有些内存缓慢增加的问题一定要在 Full GC 前 dump。

期间我们还发现缓存相关的对象占用内存较高,但是经过分析,缓存对象生命周期本身就比较长,所以常驻在堆上,没有问题,继续看。

排查发现 Metaspace 内存占用是随着双 12 新接口压测流量的增长而增长,所以可以确定是新接口代码引入。

java 中双 12 压测引出的线上 Full GC 怎么排查

分析 GC dump 日志发现可疑点,同一个 ClassCloader 加载了接近 3000 个,如下图所示,前面我们说过,ClassCloader 信息在 Metaspace 区域。

java 中双 12 压测引出的线上 Full GC 怎么排查破案了,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 小编会继续努力为大家带来更多实用的文章!