JVM调优-动态代理导致的GC频繁
JVM调优-动态代理导致的GC频繁
# 一、问题现象描述
上线接口前,系统做压力测试时,使用10并发进行压测,CPU使用率到了100%,此时系统最大QPS才200多。通过JVM监控查看JVM youngGC很频繁,fullGC数量为零。
# 二、排查分析问题过程
# 2.1 找到CPU使用率最高的进程
直接通过linux命令 top查看,找到对应的进程ID,发现正是压测的java系统进程ID,找到进程ID后,然后在查找该进程下CPU使用率最高是哪个线程,可以通过top -p 进程ID -H 命令显示线程使用cpu信息,具体的截图我这里就不放了。总之会拿到一个pid。
PID列则为十进制显示的线程ID,然后转换为16进制通过jstack 系统进程ID。
# 2.2 打印CPU使用率最高的进程信息
jstack 222 | grep 15e
执行完命令之后,可以看到一个 Finalizer 线程。
Finalizer线程是个单一职责的线程。这个线程会不停的循环等待java.lang.ref.Finalizer.ReferenceQueue中的新增对象。一旦Finalizer线程发现队列中出现了新的对象,它会弹出该对象,调用它的finalize()方法,将该引用从Finalizer类中移除,因此下次GC再执行的时候,这个Finalizer实例以及它引用的那个对象就可以回垃圾回收掉了。
说明Finalizer的队列中有许多的等待回收的垃圾对象,可以通过命令查看等待回收的对象都有哪些。
# 2.3 打印相关的内存信息
jmap -finalizerinfo 进程ID
执行命令后显示结果如下
Count Class description
-------------------------------------------------------
32221 com.XXXXXXX$$EnhancerByCGLIB$$200e6ee6
14908 com.XXXXXXXX$$EnhancerByCGLIB$$a59933de
11982 java.util.zip.Deflater
1 java.net.SocksSocketImpl
2
3
4
5
6
发现有好多的自定义对象,通过类名可以看到这些对象都是通过CGLIB动态代理创建的,而这些动态代理类都默认实现了finalize方法,导致这些对象在进行垃圾回收时必须先要执行finalize方法,所以都积压到了finalizer的队列中。
# 三、原因和解决方案
1、不要使用cglib来给那些需要频繁进行垃圾回收的对象创建动态代理,这些对象大量创建的同时,也会创建相等数量的动态代理对象,使得内存占用迅速增长,并且不断进行垃圾回收,由于代理类重写了finalize方法,给垃圾回收带来了额外的压力。
2、尽量能够复用对象,不要每次都new一个对象