[JVM]OOM实例分析

本文内容总结自《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》中的第2章。

  • 通过设置虚拟机的启动参数来模拟内存区域溢出的情况
  • 通过异常信息判断出是哪个内存溢出了,知道什么样的代码可能会导致内存溢出
Java内存区域
  • 设置虚拟机的启动参数

    在使用代码进行验证之前,我们需要先了解如何设置虚拟机的启动参数,设置方法有两种。

  • 使用命令行执行程序时
    使用命令行运行程序时,直接在java后面跟上参数和程序名称

D:\>javac HeapOOM.java

D:\>java HeapOOM

D:\>java -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError HeapOOM
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid9512.hprof ...
Heap dump file created [27898328 bytes in 0.121 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOf(Arrays.java:2245)
        at java.util.Arrays.copyOf(Arrays.java:2219)
        at java.util.ArrayList.grow(ArrayList.java:242)
        at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)
        at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)
        at java.util.ArrayList.add(ArrayList.java:440)
        at HeapOOM.main(HeapOOM.java:16)
  • 使用IDE执行程序时
    直接在VM Arguments中设置
使用IDE执行程序
  • Java堆溢出

  • VM arguments
    设置Java堆的大小20MB,将堆的最小值-Xms参数和最大值-Xmx参数设置为一样,避免堆自动扩展。通过设置参数-XX:+HeapDumpOnOutOfMemoryError,在发生内存异常时可以生成堆转存储的dump文件,通过Eclipse Memory Analyzer (MAT)工具可以分析该文件。
 VM Args:
 -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError  
Option Description
-Xms20M -Xmx20M to specify that a maximum heap size of 20 Mbytes
-XX:+HeapDumpOnOutOfMemoryError Dump heap to file when java.lang.OutOfMemoryError is thrown
  • Code
    根据Java内存区域可知,通过new指令创建的对象是放到Java堆中的。下面这段代码的功能就是一直在创建OOMObject对象,当OOMObject对象占用大小超出设置的20M时,就会报出OutOfMemoryError异常,并提示发生异常的区域为"Java heap space"
import java.util.ArrayList;
import java.util.List;

/**
 * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError  
 */
public class HeapOOM {
    static class OOMObject {

    }

    public static void main(String[] agrs) {
        List<OOMObject> list = new ArrayList<OOMObject>();

        while (true) {
            list.add(new OOMObject());
        }
    }
}
  • Output
    发生异常后生产的dump文件的默认名称为"java_pid进程id.hprof",默认生成路径为工程的根目录
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid9512.hprof ...
Heap dump file created [15782234 bytes in 0.074 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2760)
    at java.util.Arrays.copyOf(Arrays.java:2734)
    at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
    at java.util.ArrayList.add(ArrayList.java:351)
    at HeapOOM.main(HeapOOM.java:16)

使用MAT打开 java_pid9512.hprof,可以生成一个分析报告

使用MAT打开 java_pid9512.hprof

点击进入Dominator Tree界面,可以将对象占用Heap大小进行排序,由下图可以看出main thread里面生成了很多HeapOOM$OOMObject对象,导致暂用了97.49%的内存。

Dominator Tree
  • 虚拟机栈溢出

  • VM arguments
    使用-Xss参数减少栈内存的容量
 VM Args:
 -Xss128k
  • Code
    stackLeak()方法不断地自己调用自己,导致栈帧的深度一直在增加,当超出栈内存容量时,将会抛出java.lang.StackOverflowError异常
/**
 * VM Args:-Xss128k
 */
public class JavaVMStackSOF {
    private int stackLength = 1;

    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    /**
     * @param args
     * @throws Throwable
     */
    public static void main(String[] args) throws Throwable {
        // TODO Auto-generated method stub
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            // TODO: handle exception
            System.out.println("stack length : " + oom.stackLength);
            throw e;
        }
    }

}
  • Output
stack length : 11432
Exception in thread "main" java.lang.StackOverflowError
    at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:8)
    at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:9)
    at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:9)
    ... ...
  • 方法区和运行时常量池异常

运行时常量池是方法区的一部分,而方法区属于"永久代"(PermGen space)

  • VM arguments
    通过-XX:PermSize和 -XX:MaxPermSize限制永久代的大小
  VM Args:
  -XX:PermSize=10M -XX:MaxPermSize=10M
  • Code
    String.intern()的作用:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回常量池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。
    下面这段代码一直在往常量池中添加字符串,直到超出常量池的容量。
import java.util.ArrayList;
import java.util.List;

/**
 * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
 */
public class RuntimeConstantPoolOOM {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        List<String> list = new ArrayList<String>();
        int i = 0;
        while (true) {
            list.add(String.valueOf(i++).intern());
        }
    }

}
  • Output
  • JDK 1.6及之前的版本中,常量池分配在永久代内,可以通过-XX:PermSize和 -XX:MaxPermSize限制永久代的大小,当常量池超出容量时,会抛出异常"Exception in thread "main" java.lang.OutOfMemoryError: PermGen space"

  • 由于JDK 1.7的HotSpot中,已经把原本放在永久代中的字符串常量池移除,因此使用JDK 1.7运行这段代码时不会抛出异常,while循环将一直进行下去

JDK 1.6和JDK 1.7运行结果不同
  • 本机直接内存溢出

  • VM arguments
    DirectMemory容量可以通过MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值一样(-Xmx指定)
 VM Args: 
-Xmx20M -XX:MaxDirectMemorySize=10M
  • Code
    通过Unsafe对象的allocateMemory方法申请直接内存,当超出DirectMemory容量时,抛出异常java.lang.OutOfMemoryError
import java.lang.reflect.Field;
import sun.misc.Unsafe;

/*
 * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M
 */
public class DirectMemoryOOM {
    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws IllegalArgumentException,
        IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true) {
            unsafe.allocateMemory(_1MB);
        }
    }
}
  • Output
Exception in thread "main" java.lang.OutOfMemoryError
        at sun.misc.Unsafe.allocateMemory(Native Method)
        at DirectMemoryOOM.main(DirectMemoryOOM.java:16)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容