第二章(三)OOM异常

在Java语言中,OOM异常是很常见的,而且这部分是面试的重点。所以会有部分代码示例,以供参考。从这部分内容中你会学会对JAVA问题分析。

在JVM中,除了程序计数器外,其他区域都会出现OOM异常。

如果使用终端,直接追加命令即可。如果是IDEA,按照下面的操作:


image.png

进入之后有VM options,在其中填写即可。

Java堆溢出

Java堆用于存储对象实例,只要不断的创建,且保证创建的速度大于GC的速度时,因堆容量超限而内存溢出。

示例代码

/**
 * @author lingbao08
 * @DESCRIPTION Java堆内存溢出异常测试
 * VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 * @create 2018/7/2 13:10
 **/

public class HeapOOM {

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        while (true)
            list.add(new OOMObject());
    }
    static class OOMObject{}
}

参数说明
将堆的最小值-Xms和堆的最大值-Xmx设置相同,避免堆自动扩展,即此时堆是固容。通过参数-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出时Dump出当前内存堆转储快照,便于事后分析。
运行结果

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid91169.hprof ...
Heap dump file created [27958645 bytes in 0.113 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3210)
    at java.util.Arrays.copyOf(Arrays.java:3181)
    at java.util.ArrayList.grow(ArrayList.java:261)
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
    at java.util.ArrayList.add(ArrayList.java:458)
    at com.example.demo.OOM.HeapOOM.main(HeapOOM.java:17)

异常处理
通过内存映射工具对dump出来的堆转储快照进行分析,确定内存中的对象是否必要——即是内存泄漏还是内存溢出。
如果是内存泄漏,确定泄漏对象与GCRoots之间的关系。
如果是内存溢出,则检查JVM堆参数(-Xmx和-Xms),看是否可再调大。检查某些对象的生命周期是否过长,减少内存消耗。

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

虚拟机栈溢出包含两种:

  • 若线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError。
  • 若虚拟机在扩展栈时,无法申请到足够的内存空间,抛出OutOfMemoryError。
单线程示例

示例代码
通过限制栈大小,并定义大量本地变量,增加此方法帧中本地变量表的长度。导致SOF异常。

/**
 * @author lingbao08
 * @DESCRIPTION java栈溢出
 * VM Args:-Xss 256k
 * @create 2018/7/3 10:08
 **/

public class JVMStackOverflow {

    private int stackLength = 1;

    public static void main(String[] args) throws Throwable {
        JVMStackOverflow oom = new JVMStackOverflow();
        try {
            oom.stackLength();
        }catch (Throwable e){
            System.out.println("stack length:"+oom.stackLength);
            throw e;
        }
    }

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

参数说明
-Xss设置VM栈大小(JVM默认为1M)。
在HotSpot中不区分VM栈和本地方法栈,所以-Xoss(本地方法栈大小)存在但无效。
运行结果2

stack length:1891
Exception in thread "main" java.lang.StackOverflowError
    at com.example.part2.JVMStackOverflow.stackLength(JVMStackOverflow.java:25)
......

异常处理
在单线程下,无论是栈帧太大还是VM栈容量太小,当内存无法分配时,都会抛出SOF异常。
在多线程时,通过不断的建立线程可以导致OOM异常。
操作系统分配给每个进程的内存是有限的。如Win32位的单进程是2G。

JVM总消耗内存=堆内存(Xmx)+方法区内存(MaxPermSize)+栈(和本地方法)内存(Xss)+程序计数器(很小,可以忽略不计)。

该总内存不能超过系统分配给单进程的内存,且要注意消耗内存总和不能超过系统内存。

多线程示例

示例代码
该代码段通过将栈内存设置大而导致OOM异常。
本代码段执行有风险,可能会导致系统假死或者处于高速运转中,原因见上异常处理。

/**
 * @author lingbao08
 * @DESCRIPTION 虚拟机栈与OOM异常
 * VM Args:-Xss2g
 * @create 2018/7/4 14:06
 **/

public class JVMStackOOM {

    public static void main(String[] args) {
        JVMStackOOM jvmStackOOM = new JVMStackOOM();
        jvmStackOOM.stackLeakByThread();
    }

    public void stackLeakByThread() {
        while (true) {
            Thread thread = new Thread(
                    new Runnable() {
                        public void run() {
                            dontStop();
                        }
                    }
            );
            thread.start();
        }
    }

    private void dontStop() {
        while (true) {
        }
    }
}

运行结果

java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
    at java.lang.Thread.start(Thread.java:714)
    at java.lang.ref.Reference.<clinit>(Reference.java:162)

方法区和运行时常量溢出

由于JDK1.6时,运行时常量池属于方法区的一部分,因此该区域进行如下测试。
String.intern()是一个native方法,其作用是:1)若字符串常量池中包含等于该String对象的字符串,则返回常量池中的String对象;2)若没有,将此String包含的字符串添加到常量池中,且返回此字符串对象的引用。
示例代码

/**
 * @author lingbao08
 * @DESCRIPTION 运行时常量池溢出
 * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
 * @create 2018/7/4 06:04
 **/

public class RuntimeConstantPoolOOM {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        int i = 0;
        while(true){
            list.add(String.valueOf(i).intern());
        }
    }
}

参数说明
-XX:PermSize=10M设置方法区初始内存。
-XX:MaxPermSize=10M设置方法区最大内存。
运行结果
JDK6中:

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 com.example.part2.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:18)

JDK7中,运行正常。
JDK8中:

Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=10M; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=10M; support was removed in 8.0

可以看出,从6到8的过程中,从6到7,是将字符串常量池从方法区移动到堆中,从7到8,完全移除方法区大小的参数使用。(因为~~~~)。

异常处理
扩容和检查代码。

关于常量池的另一点差异:

  String s1 = "java";
  String str1 = new String("计算机")+"软件";
  System.out.println(str1.intern()==str1);
  String str2 = new String("ja")+"va";
  System.out.println(str2.intern()==str2);

如上代码,在JDK6中,输出false,false。而在JDK7+中,输入true,false。
我们都知道new的对象在java堆中,在JDK6中,intern()方法将首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个实例的引用,所以两者不相等。而在JDK7中,intern()方法只是在常量池中记录首次出现实例的引用,所以两者指向同一对象。

Java在方法区的GC是比较苛刻的,但主要见于经常动态生成大量Class的应用或者技术中,如Spring、Hibernate等使用CGLib技术的,大量JSP或动态生成JSP文件的应用,基于OSGi的应用等。此时是需要对一些动态类进行回收的。

本机直接内存溢出

示例代码
通过Unsafe实例进行分配(这是一个底层类且不希望给非jdk类使用,所以采用反射使用)。其中unsafe.allocateMemory()方法是申请内存的。

/**
 * @author lingbao08
 * @DESCRIPTION 
 * VM Args:-XX:MaxDirectMemorySize=10m -Xmx20m
 * @create 2018/7/4 18:13
 **/

public class DirectMemoryOOM {
    public static final int _10MB = 1024 * 1024*10;

    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(_10MB);
    }
}

参数说明
-XX:MaxDirectMemorySize指定直接内存大小,默认和Java堆一样大。
运行结果
运行时间稍长。

java(92900,0x700006f66000) malloc: *** mach_vm_map(size=10485760) failed (error code=3)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
Exception in thread "main" java.lang.OutOfMemoryError
    at sun.misc.Unsafe.allocateMemory(Native Method)
    at com.example.part2.DirectMemoryOOM.main(DirectMemoryOOM.java:22)

异常处理
由DirectMemory导致的内存溢出,在堆转储文件中很难发现明显的异常,如果发现OOM异常之后Dump文件很小,而程序中又直接或间接的使用了NIO,则应考虑着一方面。

ps:DirectByteBuffer类是本机直接内存的一个底层类,且该类不公开,该类是通过计算得知内存无法分配,然后手动抛错OOM,而其真正申请分配内存的方法是Unsafe.allocateMemory()。
//TODO:该部分在后期开篇NIO时,再研究。

总结

JVM异常.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容