Abstract

Duration: 2021-03-21
Description:
马士兵 JVM训练营中再现的线上内存泄漏的demo , 模拟一个评估用户是否具备放贷资格的模型。每次去数据库取出所有用户,然后使用延迟线程每3秒对所有用户做资质评估。

Scope of influence:
造成内存泄漏,full gc频繁,内存越来越来,每次fullgc回收的内存越来越少。最终因无可回收垃圾而内存又益处,程序自杀。

示例代码

  1. public class OOM2UserCredit {
  2. private static class CardInfo{
  3. BigDecimal price = new BigDecimal(10);
  4. String name ="张三";
  5. int age=5;
  6. Date birthDay = new Date();
  7. public void m(){}
  8. }
  9. private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,new ThreadPoolExecutor.DiscardOldestPolicy());
  10. public static void main(String[] args) throws InterruptedException {
  11. executor.setMaximumPoolSize(50);
  12. while (true){
  13. // 是否符合 放贷资格
  14. modelFit();
  15. Thread.sleep(100);
  16. }
  17. }
  18. private static void modelFit(){
  19. List<CardInfo> taskList = getAllCardInfo();
  20. taskList.forEach(info-> {
  21. executor.scheduleWithFixedDelay(()->{
  22. info.m();
  23. },2,3, TimeUnit.SECONDS);
  24. });
  25. }
  26. //数据库拉取所有 信用卡用户信息
  27. private static List<CardInfo> getAllCardInfo() {
  28. List<CardInfo> list = new ArrayList<>();
  29. for (int i = 0; i < 100; i++) {
  30. CardInfo info = new CardInfo();
  31. list.add(info);
  32. }
  33. return list;
  34. }
  35. }

Failure causes

  1. 查看gc日志,发现full gc越来越频繁

image.png

  1. jvm大盘查看内存越来越少 每分钟gc次数线性增长 怀疑是内存泄漏

image.png

  1. jmap 堆导出 查看最占内存的类

    注意:当线上堆内存在1G以下,可以直接导出并重启(暂时解决内存泄漏) 如果堆内存大于2G,经实测试 MacPro i5 10多分钟没打开,i9处理器的win电脑不到1分钟打开,因此 当dump文件大于2G,应当采取其他方式,比如说运维直接操作线上机器 执行jmap ? ? | head 100 或者在预发环境模拟。

定位到FutureTask和CarInfo 这两个类的实例对象最多 占用内存最多 且只增不减少(即使fgc时也不见减少)
image.png

  1. 根据CarInfo定位到相关使用多线程部分的类,也就是示例中的OOM2UserCredit.main方法

  2. 分析目标类OOM2UserCredit

如上代码块,modelFit()这个方法是在循环体中,也就是说每0.1秒就会new一个CardInfo和FutureTask加入线程池任务队列等待执行,而任务是每3秒执行一次的,这就造成了任务堆积。因为线程拒绝策略是放弃旧任务,那么一段时间过后,线程中的线程任务数将始终维持在最大值(延迟线程池默认DelayedWorkQueue的最大任务数是Integer.MAX), 也就是说该进程会持续创建Integer.max个FutureTask和CardInfo等待 线程池执行。这么多对象被线程池的队列引用着,full gc也无法回收,于是就看到了前面的现象,gc越来越频繁,回收效果越来越差,直到OOM

Resolve procedure

  1. 修改代码

如上定位到可能的原因之后,立即修改代码.
没找到马老师的官方解答

  1. 测试验证 tcpcopy 导流到测试环境

arthas + Jprofiler remote server 查看CardInfo类full gc之后 不应该实例个数骤减

  1. 上线观察一段时间 内存稳定

Exposed problems

Improvement actions