下面给出个内存区域内存溢出的简单测试方法:
一、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参数设定。
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
- 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出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™ 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
}
}