深入理解java虚拟机

深入理解java虚拟机--实战OOM异常


本博客所有内容为阅读《深入理解java虚拟机》小结,如有侵权,请联系删除。
书接上文,本文将做几个OOM小实验,目的:通过代码验证各个运行时区域存储的内容,遇到OOM时,能根据异常信息迅速得知哪个区域OOM,以及如何处理
提前:参数设置-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

Java堆溢出

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

程序分析:这个小程序说的是一直往list里面放对象,因为对象是存储在堆里面,而我们设置的堆的大小为20M,且不可扩展(堆的最小值-Xms和-Xmx一样),并且设置GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象。这样随着不断往list里面加对象,最终导致堆内存被占满,导致OOM。程序输出如下:

/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/bin/java -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError "-javaagent:/Applications/IntelliJ IDEA Edu.app/Contents/lib/idea_rt.jar=62647:/Applications/IntelliJ IDEA Edu.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/lib/tools.jar:/Users/yiwang/projects/javaprojects/jvm_book-master/out/production/jvm_book-master org.fenixsoft.jvm.chapter2.HeapOOM
[GC (Allocation Failure) [PSYoungGen: 8192K->1008K(9216K)] 8192K->5121K(19456K), 0.0066824 secs] [Times: user=0.05 sys=0.01, real=0.01 secs] 
[GC (Allocation Failure) --[PSYoungGen: 9200K->9200K(9216K)] 13313K->19432K(19456K), 0.0127850 secs] [Times: user=0.08 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 9200K->0K(9216K)] [ParOldGen: 10232K->9793K(10240K)] 19432K->9793K(19456K), [Metaspace: 3032K->3032K(1056768K)], 0.1366399 secs] [Times: user=0.73 sys=0.01, real=0.14 secs] 
[Full GC (Ergonomics) [PSYoungGen: 7704K->7996K(9216K)] [ParOldGen: 9793K->7683K(10240K)] 17497K->15679K(19456K), [Metaspace: 3032K->3032K(1056768K)], 0.1482688 secs] [Times: user=0.85 sys=0.00, real=0.15 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 7996K->7978K(9216K)] [ParOldGen: 7683K->7683K(10240K)] 15679K->15662K(19456K), [Metaspace: 3032K->3032K(1056768K)], 0.1055929 secs] [Times: user=0.83 sys=0.01, real=0.11 secs] 
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid13449.hprof ...
Heap dump file created [27743976 bytes in 0.082 secs]
Heap
 PSYoungGen      total 9216K, used 8192K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 8192K, 100% used [0x00000007bf600000,0x00000007bfe00000,0x00000007bfe00000)
  from space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
  to   space 1024K, 49% used [0x00000007bff00000,0x00000007bff7fb78,0x00000007c0000000)
 ParOldGen       total 10240K, used 7683K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  object space 10240K, 75% used [0x00000007bec00000,0x00000007bf380dd0,0x00000007bf600000)
 Metaspace       used 3078K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 339K, capacity 388K, committed 512K, reserved 1048576K
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:265)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
	at java.util.ArrayList.add(ArrayList.java:462)
	at org.fenixsoft.jvm.chapter2.HeapOOM.main(HeapOOM.java:20)

Process finished with exit code 1

出现堆内存溢出时,异常堆栈信息java.lang.OutOfMemoryError会跟随进一步提示Java heap space。
要解决这个内存区域的异常,常规的处理方法是首先通过内存印象分析工具(Eclipse MemoryAnalyzer、IDEA JProfiler)对Dump出来的堆转储快照进行分析。第一步应先确认导致OOM的对象是否是必要的,也就是先分清楚到底是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。下图显示了使用JProfiler打开的堆转储快照文件。
在这里插入图片描述

如果是内存泄漏,可以进一步通过工具查看鞋扣对象到GC Roots的引用链,找到泄漏对象是通过怎样的引用路径、与哪些GC Roots相关联,才导致垃圾器无法回收他们,根据泄漏对象的类型信息以及它与GC Roots引用链信息,一般可以比较准确的定位到这些对象创建的位置,进而找出产生内存泄漏的代码的具体位置。
如果不是内存泄漏,换句话说,内存中的对象确实都是必须存活的,则应该检查-Xms和-Xmx的设置,是否能分配更大的内存,以及检查代码,是否对象的生命周期过长,持有状态时间过长,存储结构设计不合理等。

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

验证1、创建线程时无法申请到足够的内存导致OOM,2、运行时栈容量无法容纳新的栈帧导致StackOverflow
参数设置
1、-Xss减小栈内存
情况1,虚拟机栈太小。

/**
 * VM Args:-Xss128k
 *
 * @author zzm
 */
public class JavaVMStackSOF_1 {

    private int stackLength = 1;

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

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

运行结果如图:

stack length:773
Exception in thread "main" java.lang.StackOverflowError
	at org.fenixsoft.jvm.chapter2.JavaVMStackSOF_1.stackLeak(JavaVMStackSOF_1.java:13)
	at org.fenixsoft.jvm.chapter2.JavaVMStackSOF_1.stackLeak(JavaVMStackSOF_1.java:14)

情况2、栈帧太大

/**
 * VM: JDK 1.0.2, Sun Classic VM
 *
 * @author zzm
 */
public class JavaVMStackSOF_3 {
    private static int stackLength = 0;

    public static void test() {
        long unused1, unused2, unused3, unused4, unused5,
                unused6, unused7, unused8, unused9, unused10,
                unused11, unused12, unused13, unused14, unused15,
                unused16, unused17, unused18, unused19, unused20,
                unused21, unused22, unused23, unused24, unused25,
                unused26, unused27, unused28, unused29, unused30,
                unused31, unused32, unused33, unused34, unused35,
                unused36, unused37, unused38, unused39, unused40,
                unused41, unused42, unused43, unused44, unused45,
                unused46, unused47, unused48, unused49, unused50,
                unused51, unused52, unused53, unused54, unused55,
                unused56, unused57, unused58, unused59, unused60,
                unused61, unused62, unused63, unused64, unused65,
                unused66, unused67, unused68, unused69, unused70,
                unused71, unused72, unused73, unused74, unused75,
                unused76, unused77, unused78, unused79, unused80,
                unused81, unused82, unused83, unused84, unused85,
                unused86, unused87, unused88, unused89, unused90,
                unused91, unused92, unused93, unused94, unused95,
                unused96, unused97, unused98, unused99, unused100;

        stackLength ++;
        test();

        unused1 = unused2 = unused3 = unused4 = unused5 =
                unused6 = unused7 = unused8 = unused9 = unused10 =
                        unused11 = unused12 = unused13 = unused14 = unused15 =
                                unused16 = unused17 = unused18 = unused19 = unused20 =
                                        unused21 = unused22 = unused23 = unused24 = unused25 =
                                                unused26 = unused27 = unused28 = unused29 = unused30 =
                                                        unused31 = unused32 = unused33 = unused34 = unused35 =
                                                                unused36 = unused37 = unused38 = unused39 = unused40 =
                                                                        unused41 = unused42 = unused43 = unused44 = unused45 =
                                                                                unused46 = unused47 = unused48 = unused49 = unused50 =
                                                                                        unused51 = unused52 = unused53 = unused54 = unused55 =
                                                                                                unused56 = unused57 = unused58 = unused59 = unused60 =
                                                                                                        unused61 = unused62 = unused63 = unused64 = unused65 =
                                                                                                                unused66 = unused67 = unused68 = unused69 = unused70 =
                                                                                                                        unused71 = unused72 = unused73 = unused74 = unused75 =
                                                                                                                                unused76 = unused77 = unused78 = unused79 = unused80 =
                                                                                                                                        unused81 = unused82 = unused83 = unused84 = unused85 =
                                                                                                                                                unused86 = unused87 = unused88 = unused89 = unused90 =
                                                                                                                                                        unused91 = unused92 = unused93 = unused94 = unused95 =
                                                                                                                                                                unused96 = unused97 = unused98 = unused99 = unused100 = 0;
    }

    public static void main(String[] args) {
        try {
            test();
        }catch (Error e){
            System.out.println("stack length:" + stackLength);
            throw e;
        }
    }
}

结果如下:

stack length:3449
Exception in thread "main" java.lang.StackOverflowError
	at org.fenixsoft.jvm.chapter2.JavaVMStackSOF_3.test(JavaVMStackSOF_3.java:34)
	at org.fenixsoft.jvm.chapter2.JavaVMStackSOF_3.test(JavaVMStackSOF_3.java:34)
	at org.fenixsoft.jvm.chapter2.JavaVMStackSOF_3.test(JavaVMStackSOF_3.java:34)
	at org.fenixsoft.jvm.chapter2.JavaVMStackSOF_3.test(JavaVMStackSOF_3.java:34)
	at org.fenixsoft.jvm.chapter2.JavaVMStackSOF_3.test(JavaVMStackSOF_3.java:34)
	at org.fenixsoft.jvm.chapter2.JavaVMStackSOF_3.test(JavaVMStackSOF_3.java:34)
	at org.fenixsoft.jvm.chapter2.JavaVMStackSOF_3.test(JavaVMStackSOF_3.java:34)

结论:无论是虚拟机栈太小,还是栈帧太大,都会导致StackOverflow
情况3、如果单线程栈分配内存过大,则能够启动的线程就会减少,如果强行启动过大线程,会导致OOM。该实验有风险,慎重做。

/**
 * VM Args:-Xss20M (这时候不妨设大些,请在32位系统下运行)
 *
 * @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();
    }
}

运行一段时间后,笔者的笔记本风扇开始疯狂转动,笔者就赶紧终止了实验,并没有得到书中的实验现象,笔者得到的实验现象如下:

Java HotSpot(TM) 64-Bit Server VM warning: Exception java.lang.OutOfMemoryError occurred dispatching signal SIGINT to handler- the VM may need to be forcibly terminated

出现StackOverflowError异常时,会有明确错误堆栈可供分析,相对而言比较容易定位到问题所 在。如果使用HotSpot虚拟机默认参数,栈深度在大多数情况下(因为每个方法压入栈的帧大小并不是 一样的,所以只能说大多数情况下)到达1000~2000是完全没有问题,对于正常的方法调用(包括不能 做尾递归优化的递归调用),这个深度应该完全够用了。但是,如果是建立过多线程导致的内存溢 出,在不能减少线程数量或者更换64位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取 更多的线程。这种通过“减少内存”的手段来解决内存溢出的方式,如果没有这方面处理经验,一般比 较难以想到,这一点读者需要在开发32位系统的多线程应用时注意。也是由于这种问题较为隐蔽,从 JDK 7起,以上提示信息中“unable to create native thread”后面,虚拟机会特别注明原因可能是“possibly out of memory or process/resource limits reached”。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值