参考
分析工具:https://www.cnblogs.com/zhangyaxiao/p/6678385.html https://blog.youkuaiyun.com/adermxl/article/details/37725151
实战:https://blog.youkuaiyun.com/liu765023051/article/details/75127361
1.Java堆溢出
不断创建对象,并保证GC Roots到这些对象之间有可达路径来避免垃圾回收,就能导致堆内存占用达到最大值产生内存溢出.
//VM ARGS: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
public class Main {
static class OOMObject {}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while(true) {
list.add(new OOMObject());
}
}
}
程序产生以下异常
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid11164.hprof ...
Heap dump file created [28348961 bytes in 0.395 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)
2.虚拟机栈和本地方法栈溢出
HotSpot虚拟机并不区分虚拟机栈和本地方法栈,因此设置-Xoss参数没有用。栈容量值由-Xss参数设置。
虚拟机栈中有两种异常:
若线程请求的栈深度大于虚拟机所允许的最大深度,抛出StackOverflowError.
若虚拟机在扩展时无法申请到足够内存,抛出OutOfMemoryError
以下例子抛出StackOverflowError
//VM ARGS: -Xss128k -XX:+HeapDumpOnOutOfMemoryError
public class Main {
private int statckLength = 1;
public void stackLeak() {
statckLength++;
stackLeak(); //通过无限递归导致虚拟机栈溢出
}
public static void main(String[] args) {
Main app = new Main();
try {
app.stackLeak();
} catch (Throwable e) {
System.out.println("stack length:"+app.statckLength);
throw e;
}
}
}
此时抛出了OOM异常,并打印了调用次数为998
stack length:998
Exception in thread "main" java.lang.StackOverflowError
at Main.stackLeak(Main.java:9)
at Main.stackLeak(Main.java:10)
at Main.stackLeak(Main.java:10)
注意:
单线程情况下,虚拟机栈容量太小,会导致StackOverflowError或OutOfMemoryError,
但是在多线程中,虚拟机栈容量太大,反而可能导致OutOfMemoryError,因为虚拟机栈容量越大,支持的线程数越少,最终就会因为线程过多而导致物理内存耗尽。
如下多线程代码会导致上面这种情况发生:
//VM ARGS: -Xss2M -XX:+HeapDumpOnOutOfMemoryError
public class Main {
public 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) {
Main app = new Main();
app.stackLeakByThread(); //执行会导致死机
}
}
3.运行时常量池溢出
运行时常量池属于方法区的一部分,线程共享。
注意HotSpot从JDK1.7开始,已经没有永久代的分区概念,因此以前可能发生的一些OOM,现在可能不会发生了,例如下面代码。
//VM ARGS: -XX:PermSize=10M -XX:MaxPermSize=10M -XX:+HeapDumpOnOutOfMemoryError
public class Main {
public static void main(String[] args) {
//使用List保持常量池引用,避免Full GC回收常量池
List<String> list = new ArrayList<String>();
//10M的PermSize在integer范围内足够产生OOM了
int i=0;
while(true) {
list.add(String.valueOf(i++).intern());
}
}
}
这段代码在JDK1.6中会抛出OutOfMemoryError: PermGen space异常,
但是在JDK1.8中并没有抛异常,而是一直在循环执行。
另外,关于“永久代”分区,在JDK1.6中,和JDK1.7及以上的版本中,对String.intern()的处理略有不同,因为1.7开始已经没有永久代的说法了。例如下面
public class Main {
public static void main(String[] args) {
String str1 = new StringBuilder("计算机").append("软件").toString();
System.out.println(str1.intern() == str1); //1.6中false,1.7中true
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2); //1.6中false,1.7中false
}
}
上面代码在1.6中会得到两个false,在1.7中则是一个true,一个false,原因是HotSpot没有了永久代,导致堆String.intern()的处理不同。
1.6中,新对象在首次创建时会拷贝进永久代,所以与堆中的对象比较返回false
1.7中,没有了永久代的说法,也不会拷贝对象了,只会在常量池中记录首次出现的实例引用,因而上面对比的其实是一个对象,故第一个对比返回true。
第二个对比返回false是因为常量池中已经有java这个字符串了,不属于首次创建,intern()返回的对象跟堆中的不是同一个对象。
4.方法区溢出
方法区用于存放Class相关信息,例如 类名,访问修饰符,常量池,字段描述,方法描述等。
实时编译生成的代码也保存在方法区,所以如Spring,Hibernate等框架使用了动态代理来对类增强时,可能会导致方法区内存溢出。
5.本机直接内存溢出
本机直接内存溢出的显著特征是在Heap Dump文件中不会看见明细异常,如果OOM后Dump很小,而程序中又直接或间接使用了NIO,那可能就是发生了直接内存溢出。
1万+

被折叠的 条评论
为什么被折叠?



