`

实战OutOfMemoryError(转)

阅读更多

实战 OutOfMemoryError

上述区域中,除了程序计数器,其他在 VM Spec 中都描述了产生 OutOfMemoryError (下称 OOM )的情形,那我们就实战模拟一下,通过几段简单的代码,令对应的区域产生 OOM 异常以便加深认识,同时初步介绍一些与内存相关的虚拟机参数。下文的代码都是基于 Sun Hotspot 虚拟机 1.6 版的实现,对于不同公司的不同版本的虚拟机,参数与程序运行结果可能结果会有所差别。

 

Java

 

Java 堆存放的是对象实例,因此只要不断建立对象,并且保证 GC Roots 到对象之间有可达路径即可产生 OOM 异常。测试中限制 Java 堆大小为 20M ,不可扩展,通过参数 -XX:+HeapDumpOnOutOfMemoryError 让虚拟机在出现 OOM 异常的时候 Dump 出内存映像以便分析。(关于 Dump 映像文件分析方面的内容,可参见本文第三章《 JVM 内存管理:深入 JVM 内存异常分析与调优》。)

清单 1 Java OOM 测试

/**

  * VM Args -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

  * @author zzm

  */

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.lang.OutOfMemoryError: Java heap space

Dumping heap to java_pid3404.hprof ...

Heap dump file created [22045981 bytes in 0.663 secs]

 

 

VM 栈和本地方法栈

 

Hotspot 虚拟机并不区分 VM 栈和本地方法栈,因此 -Xoss 参数实际上是无效的,栈容量只由 -Xss 参数设定。关于 VM 栈和本地方法栈在 VM Spec 描述了两种异常: StackOverflowError OutOfMemoryError ,当栈空间无法继续分配分配时,到底是内存太小还是栈太大其实某种意义上是对同一件事情的两种描述而已,在笔者的实验中,对于单线程应用尝试下面 3 种方法均无法让虚拟机产生 OOM ,全部尝试结果都是获得 SOF 异常。

 

1. 使用 -Xss 参数削减栈内存容量。结果:抛出 SOF 异常时的堆栈深度相应缩小。

2. 定义大量的本地变量,增大此方法对应帧的长度。结果:抛出 SOF 异常时的堆栈深度相应缩小。

3. 创建几个定义很多本地变量的复杂对象,打开逃逸分析和标量替换选项,使得 JIT 编译器允许对象拆分后在栈中分配。结果:实际效果同第二点。

 

清单 2 VM 栈和本地方法栈 OOM 测试(仅作为第 1 点测试程序)

/**

  * VM Args -Xss128k

  * @author zzm

  */

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);

                     throw e;

              }

       }

}

 

运行结果:

stack length:2402

Exception in thread "main" java.lang.StackOverflowError

        at org.fenixsoft.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:20)

        at org.fenixsoft.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:21)

        at org.fenixsoft.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:21)

 

如果在多线程环境下,不断建立线程倒是可以产生 OOM 异常,但是基本上这个异常和 VM 栈空间够不够关系没有直接关系,甚至是给每个线程的 VM 栈分配的内存越多反而越容易产生这个 OOM 异常。

 

原因其实很好理解,操作系统分配给每个进程的内存是有限制的,譬如 32 Windows 限制为 2G Java 堆和方法区的大小 JVM 有参数可以限制最大值,那剩余的内存为 2G (操作系统限制) -Xmx (最大堆) -MaxPermSize (最大方法区),程序计数器消耗内存很小,可以忽略掉,那虚拟机进程本身耗费的内存不计算的话,剩下的内存就供每一个线程的 VM 栈和本地方法栈瓜分了,那自然每个线程中 VM 栈分配内存越多,就越容易把剩下的内存耗尽。

 

清单 3 :创建线程导致 OOM 异常

/**

  * VM Args -Xss2M (这时候不妨设大些)

  * @author zzm

  */

public class JavaVMStackOOM {

 

       private void dontStop() {

              while (true) {

              }

       }

 

       public void stackLeakByThread() {

              while (true) {

                     Thread thread = new Thread(new Runnable() {

                            @Override

                            public void run() {

                                   dontStop();

                            }

                     });

                     thread.start();

              }

       }

 

       public static void main(String[] args) throws Throwable {

              JavaVMStackOOM oom = new JavaVMStackOOM();

              oom.stackLeakByThread();

       }

}

 

特别提示一下,如果读者要运行上面这段代码,记得要存盘当前工作,上述代码执行时有很大令操作系统卡死的风险。

 

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread



运行时常量池

 

要在常量池里添加内容,最简单的就是使用 String.intern() 这个 Native 方法。由于常量池分配在方法区内,我们只需要通过 -XX:PermSize -XX:MaxPermSize 限制方法区大小即可限制常量池容量。实现代码如下:

 

清单 4 :运行时常量池导致的 OOM 异常

/**

  * VM Args -XX:PermSize=10M -XX:MaxPermSize=10M

  * @author zzm

  */

public class RuntimeConstantPoolOOM {

 

       public static void main(String[] args) {

              // 使用 List 保持着常量池引用,压制 Full GC 回收常量池行为

              List<String> list = new ArrayList<String>();

              // 10M PermSize integer 范围内足够产生 OOM

              int i = 0;

              while (true) {

                     list.add(String.valueOf(i++).intern());

              }

       }

}

 

运行结果:

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 直接操作字节码动态生成大量的 Class ,值得注意的是,这里我们这个例子中模拟的场景其实经常会在实际应用中出现:当前很多主流框架,如 Spring Hibernate 对类进行增强时,都会使用到 CGLib 这类字节码技术,当增强的类越多,就需要越大的方法区用于保证动态生成的 Class 可以加载入内存。

 

清单 5 :借助 CGLib 使得方法区出现 OOM 异常

/**

  * VM Args -XX:PermSize=10M -XX:MaxPermSize=10M

  * @author zzm

  */

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 {

 

       }

}

 

运行结果:

Caused by: java.lang.OutOfMemoryError: PermGen space

       at java.lang.ClassLoader.defineClass1(Native Method)

       at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)

       at java.lang.ClassLoader.defineClass(ClassLoader.java:616)

       ... 8 more

 

本机直接内存

 

DirectMemory 容量可通过 -XX:MaxDirectMemorySize 指定,不指定的话默认与 Java 堆( -Xmx 指定)一样,下文代码越过了 DirectByteBuffer ,直接通过反射获取 Unsafe 实例进行内存分配( Unsafe 类的 getUnsafe() 方法限制了只有引导类加载器才会返回实例,也就是基本上只有 rt.jar 里面的类的才能使用),因为 DirectByteBuffer 也会抛 OOM 异常,但抛出异常时实际上并没有真正向操作系统申请分配内存,而是通过计算得知无法分配既会抛出,真正申请分配的方法是 unsafe.allocateMemory()

 

/**

  * VM Args -Xmx20M -XX:MaxDirectMemorySize=10M

  * @author zzm

  */

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);

              }

       }

}

 

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError

       at sun.misc.Unsafe.allocateMemory(Native Method)

       at org.fenixsoft.oom.DirectMemoryOOM.main(DirectMemoryOOM.java:20)

分享到:
评论

相关推荐

    实战Java虚拟机——JVM故障诊断与性能优化

    5. **异常诊断**:书中会介绍如何通过日志、堆转储(Heap Dump)、线程转储(Thread Dump)等手段来诊断常见的JVM问题,如OutOfMemoryError、StackOverflowError等。 6. **JVM参数设置**:熟练掌握JVM启动参数的...

    JVM从入门到实战1

    JVM从入门到实战1 JVM(Java Virtual Machine)是Java程序运行的核心组件,负责将Java字节码转换为机器码并执行。在本篇文章中,我们将从 JVM的基础知识开始,逐步深入到JVM的高级主题。 JVM基础知识 JVM是Java...

    实战JAVA虚拟机 JVM故障诊断与性能优化.rar

    1. 堆内存溢出:当对象分配无法在堆内存中找到足够的空间时,会导致OutOfMemoryError。通过分析堆转储文件(Heap Dump)和设置合适的内存参数(如-Xms, -Xmx)可以定位和解决问题。 2. 栈溢出:如果线程栈空间不足,...

    实战Java虚拟机——JVM故障诊断与性能优化.pdf

    9. **异常诊断**:书中可能会涵盖如何使用JVM提供的工具来定位和处理运行时异常,包括NoClassDefFoundError、OutOfMemoryError等常见问题。 10. **JVM与操作系统交互**:JVM并非孤立存在,它与操作系统有密切联系。...

    实战JAVA虚拟机 (JVM故障诊断与性能优化)【含源码】

    例如,双亲委派模型是如何保证类加载的安全性和有序性的,如何利用JVM的内存配置避免OutofMemoryError,以及如何通过线程栈分析定位并发问题。这些内容对于理解和解决实际项目中的性能瓶颈至关重要。 JVM故障诊断是...

    Java多线程并发实战

    ### Java多线程并发实战知识点解析 #### 一、引言 在计算机科学领域,**多线程**和**并发**技术是现代软件开发中不可或缺的一部分。随着处理器核心数量的增加,利用多线程和并发可以显著提高应用程序的性能和响应...

    实战JAVA虚拟机 JVM故障诊断与性能优化

    JVM在遇到严重问题时会抛出错误(Error),如OutOfMemoryError、StackOverflowError等。理解这些错误的产生原因和解决策略,可以避免应用的突然崩溃。 十、JVM最新发展 随着技术的不断进步,JVM也在持续演进。例如...

    JVM虚拟机入门到实战资料

    掌握调优技巧能有效避免OutOfMemoryError等运行时异常,提升系统稳定性。 **五、JVM实战应用** 在实际项目中,JVM的应用不仅限于Java程序的运行。例如,通过JMX(Java Management Extensions)可以实现对远程JVM的...

    从 0 开始带你成为JVM实战高手.rar

    理解内存分配策略和垃圾回收机制,能有效避免内存泄漏和OutofMemoryError。 然后,线程共享与局部变量也是JVM的重要概念。静态变量存储在方法区,非静态变量存储在堆中。线程栈则保存每个线程的局部变量和部分常量...

    一次OOM问题排查过程实战记录

    Exception in thread http-nio-8080-exec-1027 java.lang.OutOfMemoryError: Java heap space Exception in thread http-nio-8080-exec-1031 java.lang.OutOfMemoryError: Java heap space 看线程名称应该是tomcat的...

    JVM实战-对象访问与内存溢出异常解析

    ### JVM实战-对象访问与内存溢出异常解析 #### 实验背景与目标 在Java虚拟机(JVM)中,不同的内存区域负责不同的功能,并且各自可能会出现特定类型的内存溢出异常。通过本实验,旨在深入理解JVM内存管理机制以及...

    jvm虚拟机实战

    6. **异常处理与错误**:理解Java的异常体系,包括检查异常和运行时异常,以及如何处理OutOfMemoryError等致命错误。 7. **字节码操作**:学习如何阅读和理解字节码,了解JVM指令集,这对于理解程序运行过程和优化...

    lanlan2017#JavaReadingNotes#2.4.4 本机直接内存溢出1

    - 第2章 Java内存区域与内存溢出异常- 2.4 实战:OutOfMemoryError异常2.4.4 本机直接内存溢出直接内存(Direct Memory

    Dubbo线程模型(结合Linux线程数限制配置的实战经验分享).docx

    当应用程序尝试创建超过这个限制的线程时,可能会抛出`java.lang.OutOfMemoryError: unable to create new native thread`异常。这个限制是防止恶意或意外的进程消耗过多系统资源,导致所谓的“fork炸弹”。 实战...

    jprofiler网上牛人学习实战资料汇总2

    《JProfiler实战与内存泄漏检测》 在Java开发过程中,内存泄漏是一个常见而又棘手的问题。当WEB服务频繁出现`OutOfMemoryError`时,我们往往需要深入应用内部,寻找问题的根源。本文将以JProfiler这款强大的Java...

    086、案例实战:每秒仅仅上百请求的系统为什么会因为OOM而崩溃.pdf

    我们立马登录到系统的线上机器去查看对应的日志,并看到类似下面的一句话:Exception in thread "http-nio-8080-exec-1089" java.lang.OutOfMemoryError: Java heap space。这个日志表明 Tomcat 的工作线程在处理...

Global site tag (gtag.js) - Google Analytics