内存溢出分析:OutOfMemoryError异常

来源:互联网 发布:上海北塔软件 编辑:程序博客网 时间:2024/06/04 18:50

下面给出个内存区域内存溢出的简单测试方法:

这里写图片描述

一、Java堆溢出

将堆大小限制为20MB,不可扩展(将堆堆最小值参数与最大值参数设置为一样即可避免堆自动扩展),通过参数-XX:+HeapDumpOnOutOfMemoryError可让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后进行分析。

import java.util.ArrayList;import java.util.List;/** * Created by mook on 2017/6/12. * -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError */public class HeapOOM {    static class OOMObject {}    public static void main(String[] args) {        List<OOMObject> list = new ArrayList<>();        while (true) {            list.add(new OOMObject());        }    }}

另外,由于Java堆内也可能发生内存泄露(Memory Leak),这里简要说明一下内存泄露和内存溢出的区别:

内存泄露是指分配出去的内存没有被回收回来,由于失去了对该内存区域的控制,因而造成了资源的浪费。Java中一般不会产生内存泄露,因为有垃圾回收器自动回收垃圾,但这也不绝对,当我们new了对象,并保存了其引用,但是后面一直没用它,而垃圾回收器又不会去回收它,这边会造成内存泄露,

内存溢出是指程序所需要的内存超出了系统所能分配的内存(包括动态扩展)的上限。


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

栈容量由-Xss参数设定。

  1. 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
  2. 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。
/** * Created by mook on 2017/6/12. * -Xss160k */public class JavaVMStackSOF {    private int stackLength = 1;    public void stackLeak() {        stackLength++;        stackLeak();    }    public static void main(String[] args) {        JavaVMStackSOF oom = new JavaVMStackSOF();        try {            oom.stackLeak();        } catch (Throwable e) {            System.out.println("stack length:" + oom.stackLength);            throw e;        }    }}

以上基于单线程的程序将抛出StackOverflowError异常。

如果通过不断建立线程的方式就可以产生内存溢出异常,但是这样产生的异常与栈空间是否足够大并不存在任何联系,在这种情况下,为每个线程的栈分配的内存越大,反而越容易产生内存溢出异常。
原因:
操作系统分配给每个进程的内存是有限制的。虚拟机提供参数来控制堆和方法区的内存最大值(Xmx与MaxPermSize),程序计数器消耗的内存很小,可忽略。如果虚拟机进程本身耗费的内存不计算在内,那么剩下的内存就由虚拟机栈和本地方法栈所有了。每个线程分配到的栈容量越大,可建立的线程数就越少,建立线程时就越容易把剩下的内存耗尽。
用例

如果在建立过多线程导致了内存溢出,在不能减少线程数或更换64位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。


三、方法区和运行时常量池溢出

JDK1.7开始逐步“去永久代”

/** * Created by mook on 2017/6/12. * * Returns a canonical representation for the string object. * <p> * A pool of strings, initially empty, is maintained privately by the * class {@code String}. * <p> * When the intern method is invoked, if the pool already contains a * string equal to this {@code String} object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this {@code String} object is added to the * pool and a reference to this {@code String} object is returned. * <p> * It follows that for any two strings {@code s} and {@code t}, * {@code s.intern() == t.intern()} is {@code true} * if and only if {@code s.equals(t)} is {@code true}. * <p> * All literal strings and string-valued constant expressions are * interned. String literals are defined in section 3.10.5 of the * <cite>The Java&trade; Language Specification</cite>. * * @return  a string that has the same contents as this string, but is *          guaranteed to be from a pool of unique strings. *///public native String intern();public class RuntimeConstantPoolOOM {    public static void main(String[] args) {        //intern()不会复制实例,只是在常量池中记录首次出现的实例引用。        String str1 = new StringBuilder("计算机").append("软件").toString(); //不会在常量池中存储字符串        System.out.println(str1.intern() == str1); //true        String str2 = new StringBuilder("ja").append("va").toString(); //不会在常量池中存储字符串        System.out.println(str2.intern() == str2); //false,因为常量池中本来就有"java"的引用        String str3 = "mookmao"; //直接存放在常量池中        String str4 = new String("mookmao"); //在堆内存中存放        System.out.println(str3.intern() == str3); //true        System.out.println(str3.intern() == str4); //false        System.out.println(str4.intern() == str4); //false        System.out.println(str3.intern() == str4.intern()); //true        System.out.println(str3 == str4); //false        String str5 = new String("kidcao"); //"kidcao"存放在常量池中,new String("kidcao")也会在堆内存中存放        System.out.println(str5.intern() == str5); //false    }}
原创粉丝点击