Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >剖析Java OutOfMemoryError异常

剖析Java OutOfMemoryError异常

作者头像
王金龙
发布于 2020-03-04 11:00:14
发布于 2020-03-04 11:00:14
2K00
代码可运行
举报
文章被收录于专栏:王金龙的专栏王金龙的专栏
运行总次数:0
代码可运行

剖析Java OutOfMemoryError异常

在JVM中,除了程序计数器外,虚拟机内存中的其他几个运行时区域都有发生OutOfMemoryError异常的可能,本篇就来深入剖析一下各个区域出现OOM异常的情形,以及如何解决各个区域的OOM问题。

本篇主要包括如下内容:

  • Java堆溢出
  • 运行时常量池和方法区溢出
  • 本地内存溢出

Java堆溢出

Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免JVM清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生溢出异常。

堆溢出复现

要复现这种情况也很简单:将Java堆的大小限制为固定值,且不可扩展(将堆的最小值-Xms参数与最大值-Xmx参数设置为一样即可避免堆自动扩展);当使用一个 while(true) 循环来不断创建对象就会发生 OutOfMemory,还可以使用 -XX:+HeapDumpOutofMemoryErorr 当发生 OOM 时会自动 dump 堆栈到文件中。

测试代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
        List<String> list = new ArrayList<>() ;
        while (true){
            list.add("1") ;
        }
    }

运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3210)
    at java.util.Arrays.copyOf(Arrays.java:3181)
    at java.util.ArrayList.grow(ArrayList.java:265)
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
    at java.util.ArrayList.add(ArrayList.java:462)
    at Main.main(Main.java:13)

Process finished with exit code 1

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space即是说发生了堆溢出。

原因
  1. 代码中可能存在大对象分配 ;
  2. 可能存在内存泄露,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象;
  3. 如果不是以上两种情况,也就是说内存中的对象都必须存活,就应当检查虚拟机的堆参数(-Xmx与-Xms),是否设置的堆内存空间太小,以及检查代码中是否存在某些对象声明周期过长、持有状态时间过长的情况。

上面复现代码产生堆溢出的原因主要是第三点。

解决方法
  1. 检查是否存在大对象的分配,最有可能的是大数组分配;
  2. 通过jmap命令,把堆内存dump下来,使用mat工具分析一下,检查是否存在内存泄露的问题
  3. 如果没有找到明显的内存泄露,使用 -Xmx 加大堆内存;
  4. 还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性。

运行时常量池和方法区溢出

运行时常量池是方法区的一部分,我们先对运行时常量池溢出进行测试。

运行时常量池溢出复现

最典型的使用运行时常量池的方法是String的intern()方法,该方法是一个Native方法,它的作用是:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String包含的字符串添加到常量池中,并且返回此String对象的引用。

在JDK1.6及以前的版本中,由于常量池分配在永久代中,可以通过-XX:PermSize和-XX:MaxPermSIze限制方法区大小,从而限制其中常量池的容量

测试代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        int i = 0;
        while (true) {
            list.add(String.valueOf(i++).intern());
        }
    }

笔者所用为JDK1.8,已经去除了对这两个JVM参数的支持,程序执行的结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=10m; support was removed in 8.0

Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=10m; support was removed in 8.0

暂不做深究。

方法区溢出复现

方法区用于存放class的相关信息,包括类名、访问修饰符、常量池、字段描述、方法描述等。可以通过借助CGLib直接操作字节码运行时生成大量的动态类,来填满方法区。

PermSize 和 MaxPermSize 已经不能使用了,那在JDK1.8中怎么设置方法区大小呢?

JDK 8 中将类信息移到了本地堆内存(Native Heap)中,将原有的永久代移动到了本地堆中成为 MetaSpace ,如果不指定该区域的大小,JVM 将会动态的调整。

可以使用 -XX:MaxMetaspaceSize=10M 来限制最大元空间。这样当不停的创建类时将会占满该区域并出现 OOM。

测试代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
        while (true){
            Enhancer  enhancer = new Enhancer() ;
            enhancer.setSuperclass(Main.class);
            enhancer.setUseCache(false) ;
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invoke(o,objects) ;
                }
            });
            enhancer.create() ;
        }
    }

设置好JVM参数后,执行上述代码,得到下面的额结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
    at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:530)
    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
    at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:582)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:131)
    at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319)
    at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:569)
    at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:384)
    at com.etekcity.cloud.Main.main(Main.java:27)

Process finished with exit code 1

这里的 OOM 伴随的是 Exception in thread "main" java.lang.OutOfMemoryError: Metaspace 也就是元空间溢出。

方法区溢出在应用中是比较常见的OOM异常,Spring、Hibernate等框架在对类进行增强时,都会使用到CGLib技术来增强类,增强的类越多,对方法区的容量要求就越大,就越可能出现方法区的OOM异常。

解决方法

因为该OOM原因比较简单,解决方法有如下几种:

  1. 检查是否永久代空间或者元空间设置的过小;
  2. 检查代码中是否存在大量的反射操作;
  3. dump之后通过mat检查是否存在大量由于反射生成的代理类;
  4. 重启JVM。

本机内存溢出

以上OOM异常都是出现于JVM内部,那么如果是机器本身分给JVM的内存不够导致溢出呢。

机器本身分给JVM的内存容量可以通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值(-Xmx指定一样)。

可以通过反射获取Unsafe实例进行内存分配,测试代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) throws IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true) {
            unsafe.allocateMemory(1024 * 1024);
        }
    }

运行结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Exception in thread "main" java.lang.OutOfMemoryError
    at sun.misc.Unsafe.allocateMemory(Native Method)
    at Main.main(Main.19)

有DirectMemory导致的内存溢出,在Heap Dump文件中不会看到明显的异常,如果发现OOM之后的dump文件很小,可以考虑一下是否是这方面的原因。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-02-28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
面经手册 · 第25篇《JVM内存模型总结和上手实践,亲测学完没脱发!》
最近看到一篇文章,30岁有多难。文中的一些主人公好像在学业、工作、生活、爱情等方面都过的都不如意。要不是错过这,要不是走错那。总结来看,就像是很倒霉的一群倒霉蛋儿在跟生活对干!
小傅哥
2021/01/18
5240
面经手册 · 第25篇《JVM内存模型总结和上手实践,亲测学完没脱发!》
Java 常见内存溢出异常与代码实现
Java 堆 OutOfMemoryError Java 堆是用来存储对象实例的, 因此如果我们不断地创建对象, 并且保证 GC Root 和创建的对象之间有可达路径以免对象被垃圾回收, 那么当创建的对象过多时, 会导致 heap 内存不足, 进而引发 OutOfMemoryError 异常. /** * @author xiongyongshun * VM Args: java -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError */ public c
用户1212940
2018/01/23
7760
JVM参数详解及OOM
JVM参数 堆的限制 JVM中最大堆大小有三方面限制: 相关操作系统的数据模型(32-bt还是64-bit)限制 系统的可用虚拟内存限制 系统的可用物理内存限制 32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制 堆参数 -Xmx: 指定JVM的最大堆大小,如:-Xmx=2g -Xms: 指定JVM的最小堆大小,如:-Xms=2g,高并发应用,建议和-Xmx一样,防止因为内存收缩/突然增大带来的性能影响。 -Xmn: 指定JVM中NewGeneration的大小,如:-Xmn256m。这个参
用户1263954
2018/01/30
3.4K0
JVM-03内存区域与内存溢出异常(下)【OutOfMemoryError案例】
JVM参数官网 :http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html
小小工匠
2021/08/17
6280
Java 虚拟机 4:内存溢出
Java堆唯一的作用就是存储对象实例,只要保证不断创建对象并且对象不被回收,那么对象数量达到最大堆容量限制后就会产生内存溢出异常了。所以测试的时候把堆的大小固定住并且让堆不可扩展即可。测试代码如下
用户1257393
2018/07/30
7030
《深入理解java虚拟机》笔记(3)实战:OutOfMemoryError异常
针对这类异常,可通过分析工具(如Eclipse Memory Analyzer)对异常快照进行分析,找到具体发生异常代码。
夕阳也是醉了
2023/10/16
3000
手写jvm中的各种OOM
 前言     大家好,这篇blog不写什么实际技术,就把我从书上学来的,制造JVM各种OOM的方法告诉大家。下回在遇到有人问你Java会内存溢出吗?你可以快速回答他,会!我还会写各种bug,造成JVM出现OOM异常。 知己知彼,JVM的各个区域的特定 要想写出各种OOM,必须知道JVM各个区域的特点,以便针对性的写bug,造成OOM。下面是我看书后总结的JVM各个区域的特点: 区域名称 作用 是否线程私有 是否会 内存溢出 溢出原因 程序计数器 当前线程所执行的字节码的行号的指示器。 每个线程都有独立的程
温安适
2018/05/17
1.6K0
Java程序员必备:常见OOM异常分析
放假这几天,温习了深入理解Java虚拟机的第二章, 整理了JVM发生OOM异常的几种情况,并分析原因以及解决方案,希望对大家有帮助。
捡田螺的小男孩
2020/04/14
1.4K0
深入点理解JVM-JVM内存模型
CPU的主频不可能无限制的增长,要想很多的提升新能,需要多个处理器协同工作, Intel总裁的贝瑞特单膝下跪事件标志着多核时代的到来。
凯哥Java
2022/12/16
2160
深入点理解JVM-JVM内存模型
百度面试题:一个线程 OOM 后,其他线程还能运行吗?
本文代码均由笔者在基于OpenJDK 8中的HotSpot虚拟机上进行过实际测试。
JavaEdge
2021/12/07
7790
百度面试题:一个线程 OOM 后,其他线程还能运行吗?
JVM 实战 OutOfMemoryError 异常
Java堆用于储存对象实例,我们只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么随着对象数量的增加,总容量触及最大堆的容量限制后就会产生内存溢出异常。
没有故事的陈师傅
2021/09/09
3810
OOM 分析:Java 堆内存溢出
在 Java 堆中只要不断的创建对象,并且 GC-Roots 到对象之间存在引用链,这样 JVM 就不会回收对象。
爱明依
2019/04/01
1.3K0
Java 常见内存溢出异常与代码实现
Java 堆是用来存储对象实例的, 因此如果我们不断地创建对象, 并且保证 GC Root 和创建的对象之间有可达路径以免对象被垃圾回收, 那么当创建的对象过多时, 会导致 heap 内存不足, 进而引发 OutOfMemoryError 异常.
哲洛不闹
2018/09/14
7940
Java 常见内存溢出异常与代码实现
[JVM] Java 虚拟机(Java Virtual Machine)内存模型
如果使用直接指针访问方式,Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference 中直接存储的就是对象地址,如下图所示:
架构探险之道
2019/07/25
4890
二、OutOfMemoryError实战
本文通过一些可执行代码来验证异常发生的场景,并且会初步介绍几个与内存相关的最基本的虚拟机参数。 本文的主要目的有两个: 1. 通过代码验证Java虚拟机规范中描述的各个运行时区域储存的内容。 2. 希望读者在工作中遇到实际的内存溢出异常时,能根据异常的信息快速判断是哪个区域的内存溢出,知道怎样的代码可能会导致这些区域的内存溢出,以及出现这些异常后该如何处理。
栋先生
2018/09/29
7510
二、OutOfMemoryError实战
实战:OutOfMemoryError 异常(三) -- 方法区和运行时常量池溢出
运行时常量池溢出,在 OutOfMemoryError 后面跟随的提示信息 是“PermGen space”,说明运行时常量池属于方法区(HotSpot 虚拟机中的永久代)的一部 分。
Li_XiaoJin
2022/06/10
2280
实战:OutOfMemoryError 异常(三) -- 方法区和运行时常量池溢出
JVM的深入理解
JDK :英文名称(Java Development Kit),Java 开发工具包。jdk 是整个 Java 开发的核心,它集成了 jre 和一些好用的小工具。例如:javac.exe,java.exe,jar.exe 等。
用户1212940
2022/04/13
3620
JVM的深入理解
OOM异常的4种可能分析及常见的OOM异常演示
1.JAVA堆溢出 JAVA堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到这些对象之间有路径可以来避免垃圾回收机制清除这些对象,那么在对象数量达到最大堆的容量限制之后就会产生OOM异常
oktokeep
2024/10/09
4010
OOM异常的4种可能分析及常见的OOM异常演示
深入理解 JVM 之——Java 内存区域与溢出异常
本篇为深入理解 Java 虚拟机第二章内容,推荐在学习前先掌握基础的 Linux 操作、编译原理、计算机组成原理等计算机基础以及扎实的 C/C++ 功底。
浪漫主义狗
2023/09/04
3380
深入理解 JVM 之——Java 内存区域与溢出异常
JVM的基础知识点Java的内存模型
Java虚拟机是Java工程师必学的进阶功课,这段时间开始死磕JVM。今天梳理一下JVM的基础知识点Java的内存模型!
BUG弄潮儿
2020/08/11
3390
JVM的基础知识点Java的内存模型
相关推荐
面经手册 · 第25篇《JVM内存模型总结和上手实践,亲测学完没脱发!》
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验