记一次内存泄露问题

前段时间收到业务未报警, 最终定位问题为第三方代码ZkClient中存在内存泄漏。

结论:

1.       ZK分布式锁相关代码存在泄漏,当使用不断变化的字符串作为资源名称进行加锁/解锁时,这些字符串不能被回收。

2.       这个内存泄漏问题,可以通过更新kiwi-utils 包到0.0.25.4版本修复。

 

记录下排查过程:

1.       故障发生时表现为ActiveMQ消息堆积, 工作流服务器CPU占用过高, 持续idle低于10

2.       jstack 保存堆栈信息, top 查看进程的CPU,内存占用情况。 另外因为CPU占用高,用top -Hp 查看哪个线程占用CPU高, 之后先重启进程看故障是否能恢复。

3.       重启后CPU占用降低, 消息队列中积压的消息也开始正常消费,故障解除。

4.       根据重启前保存的堆栈信息和CPU占用情况, 可以很快定位到GC线程一直在占用CPU,阻塞了业务正常运行,结合重启前的内存情况,初步判断为工作流进程内存泄漏引发故障。

记一次内存泄露问题_第1张图片

 

记一次内存泄露问题_第2张图片

 

5.       在另一个生产线上, jmap -dump:live,format=b, file=dump.hprof 导出内存堆栈, 在本地用jvisualvm分析。 看到有大量用于ZK分布式锁的字符串,而相关的工作流大部分都是几天前的,基本确定这就是内存泄漏的原因, 这些字符串被kiwi-util包中的MyZkClient持有。

 

记一次内存泄露问题_第3张图片

6.       分析MyZkClient代码,发现_childListener是父类ZkClient的私有变量, 用于保存子节点的监听器。 它的数据类型为 ConcurrentHashMap>

从下面的代码里可以看到, 在执行unsubscribeChildChanges时, 只把这个listenerSet里删除了, path上没有任何listener时, 这个path会关联一个空的Set 但并没有从_childListener中删除。

记一次内存泄露问题_第4张图片

7.       确认问题后, 查看ZkClient的各历史版本, 发现一直都没有修复。因此只能通过在MyZkClient中重写unsubscribeChildChanges方法来修复。

8.       申请kiwi-utils相关svn权限, 修改代码, 发布0.0.25.4版本。

9.       更新workflow代码中的kiwi-utils版本, 部署到测试环境运行正常。

10.   线上升级workflow 观察内存情况正常。

 

你可能感兴趣的:(记一次内存泄露问题)