注:本博文主要是基于JDK1.7会适当加入1.8内容。
1、Java堆溢出
//-Xms20m -Xms20m -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());
}
}
}
运行上述代码,出现内存溢出错误即OutOfMemoryError,错误信息为参数-XX:HeapDumpOnOutOfMemoryError导出一份java_podxxxx.hprof的文件。分析dump文件,打开visualVM导入查看。(JDK中包含jvisualVM工具可进行查看)
题外话:出现OutOfMemoryError除了有内存溢出的可能性之外还存在内存泄漏的可能性。如果是内存溢出,则需要查看泄漏对象的GC Root的引用链。如果不是内存泄漏则可能是内存溢出,首先尝试调大-Xms和-Xmx,如果还是报错需要定位代码的问题(dump文件分析)
2、虚拟机栈和本地方法栈溢出
由于HotSpot虚拟机中不区分虚拟机栈和本地方法栈,一致通过-Xss调整栈内存大小。
如果线程请求的栈深度大于虚拟机所允许的最大深度则抛出StackOverflowError;如果虚拟机扩展栈内存无法申请到足够的内存空间时则抛出OutOfMemoryError。
//-Xss128k
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
JavaVMStackSOF sof = new JavaVMStackSOF();
try{
sof.stackLeak();
} catch(Throwable e) {
throw e;
}
}
}
运行上述代码,出现内存溢出错误即OutOfMemoryError。在单线程下,无论栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候虚拟机抛出的都是OutOfMemoryError。然而,在多线程情况下,是可以出现StackOverflowError,虚拟机为每个进程提供内存都是有限制的,栈内存大小约等于物理内存减去最大堆内存和方法区容量(JDK1.8已经替换为Metaspace),每个线程分配到的栈容量越大则可建立的线程数越少,建立线程就可以将剩余的内存耗尽。
//-Xss2m
public class JavaVMStackOOM {
private void doNotStop() {
while(true) {}
private void stackLeakByThread() {
while(true) {
Thread thread = new Thread(()-> doNotStop());
thread.start();
}
}
public static void main(String[] args) {
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}
}
}
注意:这段代码如果执行在Windows操作系统中,会导致系统假死现象,原因是Java的线程会映射到操作系统的内核线程上(需要重启机器)。
3、方法区和运行时常量池溢出(运行时常量池JDK1.7移除方法区,JDK1.8已经移除方法区设置Metaspace)
请参考上一章String.intern代码解释。
4、直接内存溢出
直接内存容量可以通过参数-XX:MaxDirectMemorySize指定,若不指定则默认与Java堆最大值(-Xmx)一样。直接内存溢出明显特征是Heap Dump文件中不会看见明显的异常排除堆内存溢出。