哪部分区域、什么样的代码和操作可能导致内存溢出异常?(实战: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

相关文章

JAVA项目swing转javafx语法规则以及示例代码

《JAVA项目swing转javafx语法规则以及示例代码》:本文主要介绍JAVA项目swing转javafx语法规则以及示例代码的相关资料,文中详细讲解了主类继承、窗口创建、布局管理、控件替换、... 目录最常用的“一行换一行”速查表(直接全局替换)实际转换示例(JFramejs → JavaFX)迁移建

Go异常处理、泛型和文件操作实例代码

《Go异常处理、泛型和文件操作实例代码》Go语言的异常处理机制与传统的面向对象语言(如Java、C#)所使用的try-catch结构有所不同,它采用了自己独特的设计理念和方法,:本文主要介绍Go异... 目录一:异常处理常见的异常处理向上抛中断程序恢复程序二:泛型泛型函数泛型结构体泛型切片泛型 map三:文

解决idea启动项目报错java: OutOfMemoryError: insufficient memory

《解决idea启动项目报错java:OutOfMemoryError:insufficientmemory》:本文主要介绍解决idea启动项目报错java:OutOfMemoryError... 目录原因:解决:总结 原因:在Java中遇到OutOfMemoryError: insufficient me

maven异常Invalid bound statement(not found)的问题解决

《maven异常Invalidboundstatement(notfound)的问题解决》本文详细介绍了Maven项目中常见的Invalidboundstatement异常及其解决方案,文中通过... 目录Maven异常:Invalid bound statement (not found) 详解问题描述可

MyBatis中的两种参数传递类型详解(示例代码)

《MyBatis中的两种参数传递类型详解(示例代码)》文章介绍了MyBatis中传递多个参数的两种方式,使用Map和使用@Param注解或封装POJO,Map方式适用于动态、不固定的参数,但可读性和安... 目录✅ android方式一:使用Map<String, Object>✅ 方式二:使用@Param

SpringBoot实现图形验证码的示例代码

《SpringBoot实现图形验证码的示例代码》验证码的实现方式有很多,可以由前端实现,也可以由后端进行实现,也有很多的插件和工具包可以使用,在这里,我们使用Hutool提供的小工具实现,本文介绍Sp... 目录项目创建前端代码实现约定前后端交互接口需求分析接口定义Hutool工具实现服务器端代码引入依赖获

Java 队列Queue从原理到实战指南

《Java队列Queue从原理到实战指南》本文介绍了Java中队列(Queue)的底层实现、常见方法及其区别,通过LinkedList和ArrayDeque的实现,以及循环队列的概念,展示了如何高效... 目录一、队列的认识队列的底层与集合框架常见的队列方法插入元素方法对比(add和offer)移除元素方法

利用Python在万圣节实现比心弹窗告白代码

《利用Python在万圣节实现比心弹窗告白代码》:本文主要介绍关于利用Python在万圣节实现比心弹窗告白代码的相关资料,每个弹窗会显示一条温馨提示,程序通过参数方程绘制爱心形状,并使用多线程技术... 目录前言效果预览要点1. 爱心曲线方程2. 显示温馨弹窗函数(详细拆解)2.1 函数定义和延迟机制2.2

Spring Boot基于 JWT 优化 Spring Security 无状态登录实战指南

《SpringBoot基于JWT优化SpringSecurity无状态登录实战指南》本文介绍如何使用JWT优化SpringSecurity实现无状态登录,提高接口安全性,并通过实际操作步骤... 目录Spring Boot 实战:基于 JWT 优化 Spring Security 无状态登录一、先搞懂:为什

C++11中的包装器实战案例

《C++11中的包装器实战案例》本文给大家介绍C++11中的包装器实战案例,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录引言1.std::function1.1.什么是std::function1.2.核心用法1.2.1.包装普通函数1.2.