栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JVM——14.定位 Heap OOM

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JVM——14.定位 Heap OOM

文章目录
  • 1. JVM 对象分配
  • 2. 什么情况会发生 Heap OOM
  • 3. 模拟 Heap OOM
  • 4. Heap OOM 的定位及解决

1. JVM 对象分配

前面两篇文章介绍了metaspace区和Java虚拟机栈的内存溢出的分析及解决方案;一般来说,只要配置了合理的jvm参数和代码上注意一些,是不太容易发生这两块区域的内存溢出的。

真正最容易发生OOM的,是在 Java堆 中,也就是由于 平常系统中创建出来的对象实在太多了,最终导致了系统的OOM;

前面也介绍过了jvm运行时在堆中创建对象和回收的流程,在这里就只再做一个简略的对象分配回顾:

  1. 系统运行时会一直不停地创建对象,这些对象会先分配到Eden区;
  2. Eden区满了之后,就会触发YGC,然后存活对象进入Survivor区;
  3. 如果存活对象太多,Survivor区放不下的时候,就会通过分配担保进入到老年代中;
  4. 如果每次都有对象进入到老年代,也就会很快填满老年代;在老年代满了的时候,触发FGC;
  5. 如果老年代FGC之后,存活的对象还是很多,并且新生代继续往老年代进入对象;
  6. 在不能放下继续进入老年代的对象的时候,导致发生 堆空间的OOM;
2. 什么情况会发生 Heap OOM

根据上面的对象分配流程,发生堆内存溢出的原因,总结下来就是:

  • 在新生代中的对象经过YGC之后大部分都是存活的,然后进入老年代经历FGC之后,仍然大部分都是存活的;所以老年代不能再继续放入更多对象,就导致了OOM。

针对这个原因,一般来说发生堆内存溢出就主要有两种场景:

  1. 系统承载高并发请求:
    • 因为请求量很大,就会创建很多的对象;
    • 因为请求量很大,系统处理就会变慢,导致GC的时候很多对象都是存活的;
    • 所以在FGC之后大量对象存活,并且继续放入,引发OOM;
  2. 系统存在内存泄漏问题:
    • 存在内存泄漏,对于大量对象没有及时清除引用,不能被GC掉;
    • 这些对象一直占据老年代空间,如果有新生代对象进入老年代,放不下了就会引发OOM;

这是两种主要场景,其他当然还有很多比如JVM内存分配不合理什么的,但是这些并不一定会导致堆内存溢出,可能更多的是导致频繁Full GC。

3. 模拟 Heap OOM
public class HeapOomDemo {

	public static void main(String[] args) {
		int count = 0;
		List list = new ArrayList<>();

		while (true) {
			list.add(new byte[1 * 1024 * 1024]);
			System.out.println("当前创建了 " + (++count) + "M 的对象");
		}
	}
}

这段代码也很简单,就是在一个while死循环里面不停地创建对象,并且使用一个list来存储对象,也就是说这些对象全部存在引用,不能被GC掉。

JVM参数为 -Xms100m -Xmx100m,没有指定新生代大小,也就是按照默认比例分配,新生代大概30M左右,老年代大概60多M左右。

简要分析一下代码执行流程:

  1. 一直创建对象放在Eden区,在放入了快要满30M的时候(假设28M),发生YGC;这28M的对象不能被回收,全部进入老年代(老年代28M);
  2. YGC之后,继续放入Eden区,再次到28M的时候,发生YGC;继续放入老年代(老年代56M);
  3. 继续执行,继续放入Eden区,再次到28M的时候,想要发生YGC,但是此时老年代已经有56M了,距离老年代空间限度很小了,所以不能直接发生YGC;
  4. 就需要先执行FGC清除老年代的空间,但是这56M的对象,都被引用持有,在老年代也不能被GC掉,所以老年代依然占据56M空间;
  5. 此时新生代的对象还得进入老年代,但是老年代装不下了;没有办法,直接抛出OOM了;

再来看下GC日志:

从GC日志的数据呈现来看,我们上面的分析大致正确;

再来看下代码执行结果:

在创建了95M对象的时候,发生了 OutOfMemoryError: Java heap space;

4. Heap OOM 的定位及解决

发生OOM之后,往往系统会很快崩溃掉,对于所有请求都不再响应,这个时候自然要登录到服务器上检查日志文件;
在检查日志的时候,就会看到上图的 OutOfMemoryError: Java heap space提示,自然就知道了系统发生了 堆内存溢出;

并且我们是打开了jvm的dump开关的,所以日志文件中也会显示 dump出了一份名为 java_pidxxxxx.hprof 的内存快照;

有了这个,自然是使用MAT进行分析了:

  1. 打开MAT,直接查看 Actions -> Dominator Tree:

    可以看到,这里有95个大小为1M的byte[]数组对象,他们一共占用了99%左右的内存空间;

  2. 继续查看 Reports -> Leak Suspects:

    也能看出这里的95M对象;

  3. 继续点击 See stacktrace 进行查看:

    这里就看到了是 HeapOomDemo.java 类的第 21行处代码最终引发堆内存溢出的;

  4. 再回到代码检查:
    list.add(new byte[1 * 1024 * 1024]);
    也就是这行一直创建1M的byte[]数组对象的代码导致的…

通过MAT工具对于dump出来的内存快照进行分析,找到占用内存最多的对象,再定位到引发堆内存溢出的具体类和方法;

再结合代码进行分析,自然就能知道发生这次OOM的原因和解决办法了。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/314585.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号