multiproccessing 多进程 内存泄漏 多线程内存泄漏检测
转载
- 线上一个模块内存泄露了。通过一系列命令查看,有以下特征:
- 并发时才会复现
- 老年代居高不下
- CPU占用一直往上飙升
@Test
public void testStream() {
Long size = 1000000l;
Map<String, String> map = new HashMap<>();
List<String> list = new ArrayList<>();
for (long i = 0; i < size; i++) {
list.add(UUID.randomUUID().toString());
}
list.parallelStream().forEach(node -> map.put(node, node));
}
- jmap -histo:live pid 可以看到不同的类的占用的空间大小
- jstack 可以拿到当前栈的快照
- 使用 jstack 命令, 将java进程所有的线程堆栈信息输出到文件: jstack 14739 > stack.log
- 使用命令统计各线程情况:
- cat stack.log | grep "\.java" | grep explink | sort | uniq -c
- 输出结果:
- 12837 at cn.explink.b2c.weisuda.threadpool.SubExcuteWeisudaTask.run(SubExcuteWeisudaTask.java:76)
- 表示有12837个线程是在执行 SubExcuteWeisudaTask.java 的 76 行代码。
- jstat -gcutil pid interval(ms) gc查看
- 使用命令,查看 JAVA 进程创建了多少线程: ps -Te | grep java | wc
- jmap -dump:format=b,file=/tmp/dump 38648 dump内存信息然后分析:
- 首先看有没有静态变量一直引用着并且不断膨胀:没有
- 看看并发的情况下,有没有哪些地方会出问题:
- jstack发现stream里用的parallelStream一直在运行:经过看一些文章,纯粹用parallelStream应该是没有问题的,问题可能出在parallelStream+hashmap.put
- 再看看jstack,多个线程都是在运行hashmap put里。resize()的时候卡死了【是多个线程都这样吗?】
- 多个线程对同一个hashmap进行插入。(具体为什么会发生问题其实不好确定。但是可以肯定的是:多线程,没有锁,都去写,肯定有问题,其实分析到这可以确定这肯定有问题,但是不一定是这个问题导致的内存泄露,可以先修这个问题了)
- 下面猜想一下可能发生的情况
- 多个线程都发现hashmap达到一定阈值的时候需要resize。java.util.HashMap#resize
- A线程先申请新的区域,全局table设置成A申请的新区域,进行复制。
- B线程也申请了新的区域,这时候拿到的全局table是A申请的新区域,还在复制中呢,根据这个内容复制出来的不是完整的。可是这也不会爆内存啊。【此路不通】
- 还是想多线程同时put的问题,需要等到每一个都put完最终才能执行完这段代码
- 复现:利用上面的代码,跑起来,发现停不下来。用idea debug,卡死的时候按暂停,发现一直在一个找根的代码里循环出不来。所以就是树成环了。java.util.HashMap.TreeNode#root
- 最终就是通过将parallelStream换成stream解决了问题
- 这里说ForkJoinPoll.CommonPoll是有内存泄漏问题的。
- 这里有个例子可以证明hashmap不是线程安全的(一个线程写put的时候resize,东西还没复制完全呢,另一个线程读get不到)【但是这不会导致本文的问题】
- 这里说,使用parallelStream一定要避免有些任务stuck了,任务都需要在一定的时间内完成:典型的不好的例子:任务里有网络请求
本文章为转载内容,我们尊重原作者对文章享有的著作权。如有内容错误或侵权问题,欢迎原作者联系我们进行内容更正或删除文章。