哪部分区域、什么样的代码和操作可能导致内存溢出异常?(实战:OutOfMemoryError 异常)
来源:互联网 发布:淘宝统计消费 编辑:程序博客网 时间:2024/05/16 02:42
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 异常)
- Java内存区域与内存溢出异常-异常实战
- java内存区域和内存溢出异常
- Java内存区域和内存溢出异常
- Java内存区域和内存溢出异常
- java内存区域和内存溢出异常
- JVM内存区域和内存溢出异常
- JVM--内存区域和内存溢出异常
- java内存溢出异常(OutOfMemoryError)和栈溢出异常(StackOverflowError)
- 内存溢出分析:OutOfMemoryError异常
- 部分笔记—Java内存区域与内存溢出异常
- jvm(2)-OutOfMemoryError 异常(内存溢出异常)
- Java内存区域详述和内存溢出异常
- 【JVM】内存区域分配机制和内存溢出异常
- Java内存区域和内存溢出异常之一
- java内存区域和内存溢出异常--->171224
- JVM内存区域异常实战
- Java内存溢出错误:OutOfMemoryError异常分析
- [bzoj1711][Usaco2007 Open]Dining吃饭 最大流
- 桌面右下角消息提醒
- 蓝牙(一):蓝牙开发前言
- Java中介者模式(Mediator Pattern)
- webService实现之cxf
- 哪部分区域、什么样的代码和操作可能导致内存溢出异常?(实战:OutOfMemoryError 异常)
- Oracle REST Data Services (ORDS) 发布 RESTful Web Services 教程
- Jenkins之general
- eclipse中maven插件详解
- MySQL日志
- Gerrit插件开发之判断用户是否已登录的方法
- Unity3D 12-UGUI与NGUI的区别与优缺点
- springboot 整合mybatis datasourceConfig java配置
- [C#] WebSocket 客户端+服务端 轻松实现