哪部分区域、什么样的代码和操作可能导致内存溢出异常?(实战:OutOfMemoryError 异常)

本文主要是介绍哪部分区域、什么样的代码和操作可能导致内存溢出异常?(实战:OutOfMemoryError 异常),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Java内存区域与内存溢出异常

1. 概述(为什么要去了解虚拟机是怎样使用内存的?)
2. 运行时数据区域(虚拟机中的内存是如何划分的?)
3. HotSpot 虚拟机对象探秘 (HotSpot 虚拟机在 Java 堆中对象是如何创建、如何布局以及如何访问的?)
4. 实战:OutOfMemoryError 异常(哪部分区域、什么样的代码和操作可能导致内存溢出异常?)


4、哪部分区域、什么样的代码和操作可能导致内存溢出异常?

Q:为什么要学习此章内容? 两个目的!
①、通过代码验证 Java 虚拟机规范中描述的各个运行时区域存储的内容;
②、希望读者在工作中遇到实际的内存溢出异常时,能根据异常的信息快速判断是哪个区域的内存溢出,知道什么样的代码可能会导致这些区域内存溢出,以及出现这些异常后该如何处理

以下代码开头都注释了执行时所需要设置的虚拟机启动参数(注释中“VM Args”后面跟着的参数)。
如何设置虚拟机启动参数?【提醒:点击蓝色字体查看详情!】

以下的代码都是基于 Sun 公司的 HotSpot 虚拟机运行的。

4.1 Java 堆溢出

Q:怎么通过代码去验证此区域存储的内容?
Java 堆用于存储对象实例,所以只要不断地创建对象,并且保证 GC Roots 到对象之间有可达路径来避免垃圾回收机制清除这些对象。当对象数量到达最大堆的容量限制后就会产生内容溢出异常。

根据测试,只要设置最大值 -Xmx 参数即可避免堆自动扩展

import java.util.ArrayList;
import java.util.List;/*** VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError* 限制 Java 堆的大小为 20MB,不可扩展(将堆的最小值 -Xms 参数与最大值 -Xmx 参数设置为一样即可避免堆自动扩展)* 通过参数 -XX:+HeapDumpOnOutOfMemoryError 可以让虚拟机在出现内存溢出异常时 Dump 出当前的内存堆转储快照以便事后进行分析。** @author TinyDolphin*         2017/7/5 14:14.*/
public class HeapOOM {static class OOMObject {}public static void main(String[] args) {List<OOMObject> list = new ArrayList<OOMObject>();while (true) {list.add(new OOMObject());}}
}

运行结果:
这里写图片描述

结论:
当出现 Java 堆内存溢出时,异常堆栈信息“java.lang.OutOfMemoryError”会跟着进一步提示“Java heap space”

怎么解决这个区域的异常呢?
一般的手段:通过内存映像分析工具(如 Eclipse Memory Analyzer)对 Dump 出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了内存泄露(Memory Leak)还是内存溢出(Memory Overflow 即 out of memory)

如果是内存泄露,可进一步通过工具查看泄露对象到 GC Roots 的引用链。于是就能找到泄露对象是通过怎样的路径与 GC Roots 相关联并导致垃圾收集器无法自动回收它们的。

如果不存在泄露(内存中的对象确实都还必须存活着),那就应当检查虚拟机的堆参数(-Xmx 与 -Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗

4.2 虚拟机栈和本地方法栈溢出

由于在 HotSpot 虚拟机中并不区分虚拟机栈本地方法栈,因此,对于 HotSopt来说,虽然 -Xoss 参数(设置本地方法栈大小)存在,但实际上是无效的,栈容量只由 -Xss 参数设定。

Q:什么情况下,该区域会产生异常?
①、如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常
②、如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出 OutOfMemoryError 异常

/*** 虚拟机栈和本地方法栈 OOM 测试 * VM Args: -Xss128k* 设置栈内存容量为:128k* @author dolphinzhou**/
public class JavaVMStackSOF {private int stackLength = 1;public void stackLeak() {stackLength++;stackLeak();}public static void main(String[] args) throws Throwable {JavaVMStackSOF oom = new JavaVMStackSOF();try {oom.stackLeak();} catch (Throwable e) {System.out.println("stack length:" + oom.stackLength + "\n");throw e;}}
}

运行结果:
这里写图片描述

结论:
在单个线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配时,虚拟机抛出的都是 StackOverflowError 异常

如果测试时不限于单线程,通过不断的建立线程的方式倒是可以产生内存溢出异常。代码如下:

/*** 创建线程导致内存溢出异常* VM Args: -Xss2M(这时候不妨设置大些) 设置栈内存容量为:2M* @author dolphinzhou**/
public class JavaVMStackOOM {private void dontStop() {while (true) {}}public void stackLeakByThread() {while (true) {Thread thread = new Thread(new Runnable() {public void run() {dontStop();}});thread.start();}}public static void main(String[] args) throws Throwable {JavaVMStackOOM oom = new JavaVMStackOOM();oom.stackLeakByThread();}
}

运行结果: 本人的电脑虚拟机是 64 位,就算设置了很大的栈内存容量,也无法让虚拟机产生下列异常。
Exception in thread “main” java.lang.OutOfMemoryError: unable to create new native thread

Q:为什么为每个线程的栈分配的内存越大,反而越容易产生内存溢出异常?
操作系统分配给每个进程的内存是有限制的,譬如 32 位的 windows 限制为 2GB。
剩余的内存为 2GB(操作系统限制) = Xmx(最大堆容量) + MaxPermSize(最大方法区容量) + 程序计数器(消耗内存很小,可以忽略掉)+ 虚拟机进程本身消费的内存 + 虚拟机栈和本地方法栈
所以,每个线程分配到的栈容量越大,可以建立的线程数量就越少,建立线程时就越容易把剩下的内存耗尽

Q:怎么解决这个区域产生的内存溢出?
如果使用虚拟机默认参数,栈深度在大多数情况下(因为每个方法压入栈的帧大小并不是一样的,所以只能说在大多数情况下)达到 1000~2000 完全没有问题,对于正常的方法调用(包括递归),这个深度应该完全够用了。
但如果是建立过多的线程导致的内存溢出,在不能减少线程数或者更换 64 位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。

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

Q:为什么放在一块进行溢出测试?
因为运行时常量池是方法区的一部分。

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

import java.util.ArrayList;
import java.util.List;/*** VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M* 限制方法区的大小,从而间接限制其中常量池的容量* @author dolphinzhou**/
public class RuntimeConstantPoolOOM {public static void main(String[] args) {// 使用 List 保持着常量池引用,避免 Full GC 回收常量池行为List<String> list = new ArrayList<String>();// 10MB 的  PermSize 在 integer 范围内足够产生 OOM 了int i = 0;while (true) {list.add(String.valueOf(i++).intern());}}
}

运行结果:JDK1.6下才会报异常
这里写图片描述

结论:
运行时常量池溢出,会报 OutOfMemoryError: PermGen space 错误,说明运行时常量池属于方法区(HotSpot 虚拟机中的永久代)的一部分。 JDK1.7 中,不会保错。

方法区用于存放 Class 的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。
对于此区域的测试思路: 运行时产生大量的类去填满方法区,直到溢出。

/*** 借助 CGLib 使方法区出现内存溢出异常* VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M* @author dolphinzhou*/
public class JavaMethodAreaOOM {public static void main(String[] args) {while (true) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(OOMObject.class);enhancer.setUseCache(false);enhancer.setCallback(new MethodInterceptor() {public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {return proxy.invokeSuper(obj, args);}});enhancer.create();}}static class OOMObject {}
}

运行结果:

    Exception in thread "main" java.lang.OutOfMemoryError: PermGen space  at java.lang.String.intern(Native Method)  at org.fenixsoft.oom.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:18) 

方法区溢出也是一种常见的内存溢出异常。
经常生成大量 Class 的场景:程序使用了 CGLib 字节码增强和动态语言、大量 JSP 或动态产生 JSP 文件的应用、基于 OSGi 的应用等。

4.4 本机直接内存溢出

DirectMemory 容量可通过 -XX: MaxDirectMemorySize 指定,如果不指定,则默认与 Java 堆最大值(-Xmx 指定)一样。

import java.lang.reflect.Field;
import sun.misc.Unsafe;/*** VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M* @author dolphinzhou**/
public class DirectMemoryOOM {private static final int _1MB = 1024 * 1024;public static void main(String[] args) throws Exception {Field unsafeField = Unsafe.class.getDeclaredFields()[0];unsafeField.setAccessible(true);Unsafe unsafe = (Unsafe) unsafeField.get(null);while (true) {unsafe.allocateMemory(_1MB);}}
}

这里写图片描述

这篇关于哪部分区域、什么样的代码和操作可能导致内存溢出异常?(实战:OutOfMemoryError 异常)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/892818

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Python调用Orator ORM进行数据库操作

《Python调用OratorORM进行数据库操作》OratorORM是一个功能丰富且灵活的PythonORM库,旨在简化数据库操作,它支持多种数据库并提供了简洁且直观的API,下面我们就... 目录Orator ORM 主要特点安装使用示例总结Orator ORM 是一个功能丰富且灵活的 python O

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型的操作流程

《0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeekR1模型的操作流程》DeepSeekR1模型凭借其强大的自然语言处理能力,在未来具有广阔的应用前景,有望在多个领域发... 目录0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型,3步搞定一个应

Python中顺序结构和循环结构示例代码

《Python中顺序结构和循环结构示例代码》:本文主要介绍Python中的条件语句和循环语句,条件语句用于根据条件执行不同的代码块,循环语句用于重复执行一段代码,文章还详细说明了range函数的使... 目录一、条件语句(1)条件语句的定义(2)条件语句的语法(a)单分支 if(b)双分支 if-else(

轻松上手MYSQL之JSON函数实现高效数据查询与操作

《轻松上手MYSQL之JSON函数实现高效数据查询与操作》:本文主要介绍轻松上手MYSQL之JSON函数实现高效数据查询与操作的相关资料,MySQL提供了多个JSON函数,用于处理和查询JSON数... 目录一、jsON_EXTRACT 提取指定数据二、JSON_UNQUOTE 取消双引号三、JSON_KE

MySQL数据库函数之JSON_EXTRACT示例代码

《MySQL数据库函数之JSON_EXTRACT示例代码》:本文主要介绍MySQL数据库函数之JSON_EXTRACT的相关资料,JSON_EXTRACT()函数用于从JSON文档中提取值,支持对... 目录前言基本语法路径表达式示例示例 1: 提取简单值示例 2: 提取嵌套值示例 3: 提取数组中的值注意