参考:深入理解java虚拟机一书

Java虚拟机学习总结之OutOfMemoryError异常_内存溢出

开始之前,我们也应当搞清楚连个概念,内存泄漏Memory Leak 内存溢出:

内存泄漏:程序中间动态分配了内存,但是在程序结束时没有释放内存,造成这部分内存不可用。与硬件无关

内存溢出就是我们接下来要讨论的;

这篇文章的目的主要有两个:

第一:验证Java虚拟机中各个运行区域存储的内容

第二:在遇到内存溢出时能快速的根据异常信息判断是哪个区域的内存溢出

1.java堆:java堆用于存储对象实例,对象数量达到最大堆溢出

2.虚拟机栈和本地方法栈:虚拟机栈主要存放对象的引用,基本数据类型等。 本地方法栈为虚拟机使用到的Native方法服务

           虚拟机栈描述的是java方法执行的内存模型:每个方法被执行时都会同时创建一个栈帧,用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成,就对应着一个栈帧在虚拟机中从入栈到出栈的过程

3.运行时常量池:主要存放编译生成的各种字面量和符号引用,向运行时常量池中添加内容,到达最大抛出异常

4.方法区溢出:用于存放Class的相关信息,如:类名、访问修饰符、常量池、字段描述、方法描述等,GC区域也属于方法区

5.本机直接内存溢出:直接内存不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是其引用频繁,也可能导致内存溢出;

PS:下边代码注释中的JVM属性是对虚拟机的设置,

堆溢出:HeapOOM

/**
 * 异常类型:堆内存溢出
 * JVM:-Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
 * *-Xms堆的最小值参数 -Xmx堆的最大值参数 设置为一样即可避免自动扩展
 * 测试思路:不断地创建对象,使其填满堆 ,对象存放于java堆中
 * java堆用于存储对象实例,对象数量达到最大堆溢出
 * @author TH
 *PS:区分 内存泄漏 Memory Leak和内存溢出Memory Overflow
 */
public class HeapOOM {
	static class OOMObject{
		
	}
	public static void main(String[] args) {
		List<OOMObject> list = new ArrayList<OOMObject>();
		
		while(true){
			list.add(new OOMObject());
		}
	}
}

虚拟栈溢出JavaVMStackSOF

**
 * 异常类型:虚拟机栈溢出
 * JVM:-Xss128k     减少栈内存容量
 * **如果线程请求的栈深度大于虚拟机所允许的最大深度将抛出StackOverflowError异常  
 * **如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
 * 通常在建立多线程过多的情况下会发生栈溢出
 * 减少最大堆和减少栈容量可以换取更多的线程
 * @author TH
 *
 */
public class JavaVMStackSOF {
	private int stackLength = 1;
	
	public void stackLeak(){
		stackLength++;
		stackLeak();
	}
	
	public static void main(String[] args) throws Throwable{
		JavaVMStackSOF oom = new JavaVMStackSOF();
		try{
			oom.stackLeak();
		}catch(Throwable e){
			System.out.println("stack length:"+oom.stackLength);
			throw e;
		}
	}
}
运行时常量池溢出RuntimeConstantPoolOOM

/**
 * 异常类型:运行时常量池溢出
 * JVM:-XX:PermSize=10M -XX:MaxPermSize=10M  限制方法区的大小
 * 测试思路:运行时常量池主要存放编译生成的各种字面量和符号引用,向运行时常量池中添加内容,到达最大抛出异常
 *          使用String.intern这个Native方法
 * 运行时常量池属于方法区
 * @author TH
 *
 */
public class RuntimeConstantPoolOOM {
	public static void main(String[] args) {
		//使用List保持常量池的引用,避免Full GC回收常量池行为
		List<String> list = new ArrayList<String>();
		//10M的PerSize在Integer的范围内足够产生OOM
		int i=0;
		while(true){
			list.add(String.valueOf(i++).intern());
		}
	}
}

方法区溢出JavaMethodAreaOOM

public class JavaMethodAreaOOM {
	/**
	 * 异常类型:方法区溢出
	 * JVM:-XX:PermSize=10M -XX:MaxPermSize=10M
	 * 方法区作用:用于存放Class的相关信息,如:类名、访问修饰符、常量池、字段描述、方法描述等,GC区域也属于方法区
	 * 测试思路:运行大量的类去填满方法区,直到溢出
	 * 
	 * 类越多,就需要越大的方法区来保证动态生成的Class可以加载到内存
	 * 实现:借助CGLib生成
	 * @param args
	 */
	public static void main(String[] args) {
		while(true){
			Enhancer enhancer = new Enhancer();
			enhancer.setSuperclass(OOMObject.class);
			enhancer.setUseCache(false);
			enhancer.setCallback(new MethodInterceptor() {
				/**
				 * obj为由CGLib动态生成的代理类实例,
				 * method 为实体类所调用的被代理的方法的引用
				 * Object[] args为参数值列表
				 * proxy为生成的代理类对方法的代理引用
				 */
				@Override
				public Object intercept(Object obj, Method method, Object[] args,
						MethodProxy proxy) throws Throwable {
					return proxy.invokeSuper(obj, args);
				}
			});
			enhancer.create();
		}
	}
	static class OOMObject{
		
	}
}
本机直接内存溢出DirectMemoryOOM

/**
 * VM:-Xmx20M -XX:MaxDirectMemorySize=10M   指定DirectMemory容量
 * 本机直接内存溢出
 * @author TH
 *
 */
public class DirectMemoryOOM {
	private static final int _1MB = 1024 * 1024;
	public static void main(String[] args) {
		Field unsafeField = Unsafe.class.getDeclaredFields()[0];
		unsafeField.setAccessible(true);
		Unsafe unsafe = unsafeField.get(null);
		while(true){
			unsafe.allocateMemory(_1MB);
		}
	}
}