1、问题的产生
线上监控出现A(这里的别名)告警,如图:
2、问题排查
2.1 看下堆栈情况
jmap -heap 进程号
可以很直观看出永久代一直处于很高。
2.2 看下该服务相应的进程CPU情况
top -H -p 进程号
可以看出并没有因为哪个线程导致可能性的问题。
2.3 查看下gc的情况
jstat -gc 进程号 时间(毫秒)
从 gc 来看,并没有触发full gc ,但是也可以很直观看出,永久代一直是沾满的。
2.4 开始下载dump文件
方式一:对应用新增JVM启动参数
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=${目录}。
方式二:直接执行命令(强烈不推荐,尤其是非分布式环境下)
jmap -dump:format=b,file=serviceDump.dat 进程号 (注意:该方式在执行时,JVM是暂停服务的,也就是说整个java进程是停止了,所以对线上的运行会产生影响)
从永久代溢出的可能性:
①永久代溢出——方法区溢出
方法区存放Class的相关信息,下面借助CGLib直接操作字节码,生成大量的动态类。
②永久代溢出——常量池溢出
要模拟常量池溢出,可以使用String对象的intern()方法。如果常量池包含一个此String对象的字符串,就返回代表这个字符串的String对象,否则将String对象包含的字符串添加到常量池中。
楼主已经生成了相关的dump文件,是以dat结尾(serviceDump.dat)。然后用到JDK自带的工具 jvisualvm.exe
至此,我们发现产生了大量的序列化的ASMSerializer_1_ChannelMerchInfo。
重点来了!!!重点来了!!!重点来了!!!
然后马上去翻最新一次上线的代码,如图:
因为打印日志那块是核心业务,所以被执行次数会很多,因此不断地调用JsonUtil,意味着每次都new SerializeConfig()。接下来进去看其源码,首先是 new SerializeConfig(),一路跟下去,就会发现这里用了ASM动态代理。
然后继续跟 JSON.toJSONString(),再玩下走,就会发现 com.alibaba.fastjson.serializer.ASMSerializer_1_ChannelMerchInfo,和JVM分析的Dump文件内容匹配上了。
最后得出结论,系统每次都调用JSON.toJSONString的时候,都把config(即:new SerializeConfig())传进去,意味着每次都是用新的ASM代理工厂序列化新的ChannelMerchInfo代理类,所以就造成本次的问题。正常的应该是静态的ASM代理工厂,然后每次地序列化同一个ChannelMerchInfo代理类,就不会产生新的了。
3、解决办法
将new SerializeConfig()从问题方法体抽出,然后将其静态化,最后问题得以解决。