内存溢出问题排查

1. 背景

现场云库环境很卡,top显示xx进程还在,且物理内存已经达到11G(配置给xx的最大内存为8G)。查看xx日志,报错OOM
在这里插入图片描述

2. Mat内存分析工具

Mat可以用来分析内存溢出时候的dump文件,一般用到比较多的2个功能。

1) Histogram

这个功能主要查看类和对象的关系、大对象和对象之间的引用关系,打开后如下图:

在这里插入图片描述
名词解释:
Class Name:类名。
Objects:对象个数
Shallow Heap:对象本身占用内存的大小,不包含其引用的对象内存
Retained Heap:如果一个对象被释放掉,那会因为该对象的释放而减少引用进而被释放的所有的对象(包括被递归释放的)所占用的heap大小

2) Leak Suspects

Leak Suspects 界面提示可能存在内存的泄露。

在这里插入图片描述
然后,是问题一的描述,列出了一些比较大的实例。

在这里插入图片描述

3) Java编译时类的命名规范

  • 实体类的类名就是它本身,如java.util.concurrent.LinkedBlockingQueue
  • Lambda表达式命名规则一般遵循以下格式:
    KaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$̲<LambdaUniqueId…Lambda$1675类,并不是代表HardDiskICheckHandler类,而是HardDiskICheckHandler类中的Lambda表达式。
  • 内部类使用 $ 符号作为分隔符,如java.util.concurrent.LinkedBlockingQueue$Node表示java.util.concurrent.LinkedBlockingQueue类中的内部类Node。

3. 现象与分析

1) 现象

从现场传回快照文件,用mat工具打开,结果如图二、三、四所示,可以得到如下结果:

  • 如图三,java.util.concurrent.LinkedBlockingQueue类的对象占据了99.36%的内存,共计6.1G,这意味着内存溢出很可能与LinkedBlockingQueue有关
  • class com.xxx.dse.handler.HardDiskICheckHandler$$Lambda 1675 类和 j a v a . u t i l . c o n c u r r e n t . L i n k e d B l o c k i n g Q u e u e 1675类和java.util.concurrent.LinkedBlockingQueue 1675类和java.util.concurrent.LinkedBlockingQueueNode类的对象多达五千多万,并且所有的java.util.concurrent.LinkedBlockingQueue N o d e 对象的 i t e m 都为 n u l l , n e x t 全指向另一个 j a v a . u t i l . c o n c u r r e n t . L i n k e d B l o c k i n g Q u e u e Node对象的item都为null,next全指向另一个java.util.concurrent.LinkedBlockingQueue Node对象的item都为nullnext全指向另一个java.util.concurrent.LinkedBlockingQueueNode对象。

2) 分析

class com.xxx.dse.handler.HardDiskICheckHandler类中的Lambda表达式如下,其中diskCommonCheck()是HardDiskICheckHandler中的一个方法:

在这里插入图片描述

这是一个磁盘监测的方法,图五中3这个Lambda表达式用来实现某个ip环境的磁盘监测任务,它有五千多万个实例,并且和java.util.concurrent.LinkedBlockingQueue$Node实例数相等。它们之间有什么关系呢?

查看线程池Executors.newFixedThreadPool方法可知,java.util.concurrent.LinkedBlockingQueue$Node表示的是线程池任务队列的任务,也就是说线程池已经阻塞了五千多万个任务。

在这里插入图片描述

3) 猜测

已知现场48个节点,而当时系统的可用线程数是40,也就是说有8个任务被阻塞,且没有执行Lambda表达式中的iterator.next()。并且在这8个阻塞的任务被执行之前,iterator.hasNext()返回总为true,一直往任务队列中插入空任务。

4) 验证

验证的代码见附件,下图是关键代码,主要用于实现上述这种死循环下,任务队列的任务数是否一直增加:

在这里插入图片描述
运行结果:

在这里插入图片描述
总共就三个任务,执行23秒,但是却阻塞了三百多万个任务,这对内存是一个巨大的开销。

5) 复现

1、 实验组

将HardDiskICheckHandler类的Lambda表达式的线程池的核心线程数改成1,启动参数-Xmx参数改成1024后,出现了内存溢出。分析dump文件,结果如下:

在这里插入图片描述

由于家里环境只有两个节点,陷入死循环的时间不多,因此对象的个数不如现场那么多,但占据的内存也高达800M。

2、对比组

线程池的核心线程数改回Runtime.getRuntime().availableProcessors(),启动参数改为1024,没有出现内存溢出了。

4) 结论

当节点数比系统的可用线程数多的时候,就可能出现内存溢出。

5) 代码修改

HardDiskICheckHandler类中,遍历Constant.allIps的方式不用iterator,改用其他循环方式,如for循环:

在这里插入图片描述

或者

在这里插入图片描述

  • 32
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值