CGLIB 动态代理对象 GC 问题排查 | 京东云技术团队
一、问题是怎么发现的
最近有个新系统开发完成后要上线,由于系统调用量很大,所以先对核心接口进行了一次压力测试,由于核心接口中基本上只有纯内存运算,所以预估核心接口的压测 QPS 能够达到上千。
压测容器配置:4C8G
先从 10 个并发开始进行发压,结果 cpu 一下就飙升到了 100%,但是核心接口的 qps 才 200 左右。于是观察 jvm 的垃圾回收发现 younggc 很频繁,但是 fullGC 数量为零。
二、排查问题的详细过程
由于刚一开始压测,容器 cpu 就飙升到了 100%,所以需要先定位 cpu 使用率问题,找出使用 cpu 最高的几个进程。可以通过 top 命令查找进程 ID,发现正是压测的 Java 应用进程 ID;然后在定位该金晨曦 cpu 使用率最高的线程,可以通过 top -p 进程 ID -H 命令显示该进程下的线程使用 cpu 信息。
图片中 PID 列则为十进制显示的线程 ID,然后转换为 16 进制;在通过 jstack 系统进程 ID | grep 16 进制线程 ID 命令找到对应的线程信息如下,也就是该线程占用了一半左右的 cpu。
此时定位到了 Finalizer 线程,但是这个线程又有什么作用呢?
原来这个线程会不停的循环等待 java.lang.ref.Finalizer.ReferenceQueue 中的新增对象。一旦 Finalizer 线程发现队列中出现了新的对象,它会弹出该对象,调用它的 finalize()方法,将该引用从 Finalizer 类中移除,因此下次 GC 再执行的时候,这个 Finalizer 实例以及它引用的那个对象就可以被垃圾回收掉了。如果这个线程一直在不停的工作,说明 Finalizer 的队列中有许多等待 GC 的垃圾对象。此时可以通过另一个命令来查看等待回收的垃圾对象有哪些。
通过上述结果可以发现有好多的业务对象,通过类名可以看到这些对象都是通过 CGLIB 动态代理创建的,而且这些动态代理类都默认实现了 finalize 方法,导致这些对象在进行垃圾回收时必须先要执行 finalize 方法,所以都积压到了 finalizer 的队列中。
三、如何解决问题
通过上述排查过程发现,是由于大量的业务对象通过 CGLIB 创建了动态代理类,而这些代理都是系统处理请求时创建的临时对象,请求完成后,这些临时对象就需要被垃圾回收掉,从而导致 Finalizer 线程执行频繁抢占了 cpu 资源。
针对以上分析结果所以有了如下几种解决方案:
1.不要使用 CGLIB 来给那些需要频繁进行垃圾回收的对象创建动态代理,可以手动创建静态代理类。
2.对象复用,尽量减少临时对象的产生。
作者:京东零售 曹志飞
来源:京东云开发者社区
版权声明: 本文为 InfoQ 作者【京东科技开发者】的原创文章。
原文链接:【http://xie.infoq.cn/article/d7d87383fda70edab5eaea935】。文章转载请联系作者。
评论