Java堆溢出
Java堆用于存储对象实例,只要不断创建对象,保证GC Roots到对象间有可达路径避免垃圾回收机制清除这些对象
package cn.pengld;
import java.util.ArrayList;
import java.util.List;
/**
* VM Args: -Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
*/
public class HeapSpaceOOMTest {
static class OOMObject{}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true){
list.add(new OOMObject());
}
}
}
虚拟机栈和本地方法栈溢出
由于在HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,所以栈容量只由-Xss参数设定。在单个线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配时,虚拟机抛出的都是 StackOverflowError 异常。
当不限于单线程时,通过不断创建线程的方式可以产生内存溢出异常。
package cn.pengld;
/**
* VM Args: -Xss2M
*/
public class StackSpaceOOMTest {
private void keepGoing(){
while (true){
}
}
public void stackLeakByThread(){
while (true){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
keepGoing();
}
});
thread.start();
}
}
public static void main(String[] args) {
StackSpaceOOMTest stackSpaceOOMTest = new StackSpaceOOMTest();
stackSpaceOOMTest.stackLeakByThread();
}
}
运行时常量池溢出(方法区的一部分)
String类是被final修饰的不可变类,更不能被继承,这样做的目的是为了性能,程序大部分的操作是针对字符串的,将字符串放在常量池中,能有效提高程序运行的效率。若大量的创建字符串对象,会导致常量池内存不够,导致常量池OOM,这也是在处理字符串拼接时使用 StringBuilder对象替代的原因。
package cn.pengld;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ConstantPoolOOMTest {
/**
* -XX:PermSize=10M -XX:MaxPermSize=10M JDK版本号小于 1.7
* Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
*/
public static void jdk6(){
// 保持GC Roots的引用链,避免GC被回收
List<String> list = new ArrayList<>();
int i = 0;
while (true){
list.add(String.valueOf(i++).intern());
}
}
/**
*
* 自JDK 7起,原本存放在永久代的字符串常量池被移至Java堆之中
* VM Args: -Xmx6M
*/
public static void jdk7up(){
// 保持GC Roots的引用链,避免GC被回收
Set<String> list = new HashSet<>();
short i = 0;
while (true){
list.add(String.valueOf(i++).intern());
}
}
public static void main(String[] args) {
// use 1.6
// jdk6();
// use 7+
jdk7up();
}
}
特别说明:笔者还原了此种情况,运行很多次OOM都发生在HashMap的resize()方法时,但具体的OOM异常取决于具体哪里的对象分配时发生了溢出。参照 《深入理解Java虚拟机:JVM高级特性与最佳实践》(周志明)第三版 2.4.3章节
方法区溢出
方法区的主要职责是用于存放类型的相关信息,如类 名、访问修饰符、常量池、字段描述、方法描述等。基本的思路是运行时产 生大量的类去填满方法区,直到溢出为止。不过在JDK 8以后,永久代便完全退出了历史舞台,元空间作为其替代者登场。
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=10M; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=10M; support was removed in 8.0
下面演示在JDK1.6时如何制造方法区OOM。
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
package cn.pengld;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 借助CGLib使得方法区出现内存溢出异常 jdk 1.6
* VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
*/
public class MethodAreaOOMTest {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
}
static class OOMObject {
}
}
本机直接内存溢出
参照《深入理解Java虚拟机-JVM高级特性与最佳实践》(周志明)第二版 2.4.4
package cn.pengld;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
*/
public class DirectMemoryOOMTest {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
unsafe.allocateMemory(_1MB);
} }
}
Exception in thread "main" java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method) at org.fenixsoft.oom.DMOOM.main(DMOOM.java:20)