天天看点

Java虚拟机内存管理(二)

1.JVM内存溢出几种情况

  • PCR 程序计数器:用于记录正在执行的虚拟机字节码指令的地址,也是虚拟机规范中唯一未定义内存溢出的【内存区域】
  • Java虚拟机栈:每一个方法的执行都对应着一个StackFrame栈桢的入栈和出栈过程,StackFrame用于存储局部变量、操作栈、动态链接、方法出口等信息。这块内存区域定义了2种内存溢出场景:当线程请求的栈深度超过虚拟机规定的最大栈深度,就会产生 StackOverFlowError 即栈溢出的异常;当栈深度足够但线程申请不到足够内存是会产生 OutOfMemoryError 即内存溢出的情况
  • Java堆:虚拟机内存管理最重要的区域,用于存放对象实例。当堆内存不够并且扩展内存失败时会产生 OutOfMemoryError
  • 方法区:存储已经被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据,当方法区内存分配不足时也会产生 OutOfMemory
  • 运行时常量池:属于方法区一部分,存放编译期间生成的各种字面常量和符号引用,这部分内容等到类加载之后存放到方法区运行时常量池中。因为常量池具备动态性、运行期间也可以放入新的常量。因此在常量池无法申请到足够内存时,也会产生OutOfMemoryError
  • 直接内存:JDK加入NIO机制,允许基于Channel通道方式,使用Native方法直接分配堆外内存,能在一些大文件读取时显著提升性能。因为不需要在Java堆和本地堆中来回复制数据。但是本机内存会有物理限制,比如机器内存就只有8G,一旦直接内存不够时,也会发生OutOfMemoryError

 2.内存溢出情况模拟

 2.1 栈内存溢出

package out.of.memory.test;

/**
 * Java虚拟机栈:内存溢出情况模拟 StackOverFlowError + OutOfMemoryError
 * 虚拟机参数设置为:-Xss2M 即栈内存分配2M
 * 
 * @author yli
 */
public class StackTest {

	public static void main(String[] args) {
		 callSelf(0);
		
		 // 调用很容易导致系统死机...
		 // new StackTest().oom();
	}

	/**
	 * StackOverFlowError:
	 * 当线程请求栈深度超过虚拟机规定最大深度,就会产生这种异常
	 * 我们知道每一次方法调用就是StackFrame栈桢入栈和出栈过程
	 * 那么只要无限次调用方法久很容易产生栈深度不够的情况
	 * 
	 * 要模拟这种异常非常简单,很自然联想到[递归调用]就是这么一种情况
	 * 递归调用就是无限调用自己:如果没有合适的退出机制,就产生栈溢出!
	 * 
	 * @param callCount
	 */
	private static void callSelf(int callCount) {
		// 打印方法被调用多少次
		callCount++;
		System.out.println(String.format("被调用%s次!", callCount));
		
		// 只需要无限调用自己,并且不退出调用即可模拟!
		callSelf(callCount);
	}
	
	/**
	 * OutOfMemoryError:申请栈扩展内存不足导致内存溢出
	 * 不停的创建局部变量可以模拟
	 */
	private void oom() {
		while(true) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					print();
					
				}
			}).start();
		}
	}
	
	private void print(){
		while(true) {
			System.out.println(Thread.currentThread());
		}
	}
}
           

2.2 堆内存溢出

package out.of.memory.test;

import java.util.ArrayList;
import java.util.List;

/**
 * 堆内存溢出测试
 * 虚拟机参数设置为:-Xms10M -Xmx20M 即初始堆内存10M,最大堆内存20M
 * @author yli
 * 
 */
public class HeapTest {

	static class User {
		long[] numns = { 1l, 2l, 3l, 4l };
	}
	
	/**
	 * 不停创建对象,由于对象实例存储在堆内存
	 * 就能很容易模拟堆内存溢出!
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		List<User> list = new ArrayList<User>();
		while(true) {
			User u = new User();
			list.add(u);
		}
	}

}
           

2.3 常量池内存溢出

package out.of.memory.test;

import java.util.ArrayList;
import java.util.List;

/**
 * 虚拟机参数设置为:-XX:PermSize=2M -XX:MaxPersmSize=5M
 * 
 * @author yli
 */
public class ConstantPoolTest {

	/**
	 * 常量池内存溢出:只要无限构造常量即可模拟
	 * @param args
	 */
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		String s = "常量池内存溢出:只要无限构造常量即可模拟";
		int i=0;
		while(true) {
			list.add((s+String.valueOf(i++)).intern());
			System.out.println(i);
		}
	}
}
           

2.4方法区内存溢出

package out.of.memory.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * 方法区内存溢出测试
 * -XX:PermSize=2M -XX:MaxPermSize=5M
 * @author yli
 *
 */
public class MethodAreaTest {

	/**
	 * 方法区用于存储虚拟机加载的类信息(每一个类都有与之对应的class对象)
	 * 也存储常量、静态变量、动态编译后的代码等数据
	 * 常量存放在常量池,通过ConstantPoolTest测试:不停构造常量即可构造内存溢出
	 * 方法区内存溢出也可以加载大量类导致内存溢出
	 * 
	 * 
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		while(true) {
			Enhancer en = new Enhancer();
			en.setSuperclass(UserImpl.class);
			en.setUseCache(false);
			en.setCallback(new MethodInterceptor() {
				
				@Override
				public Object intercept(Object obj, Method method, Object[] args,
						MethodProxy proxy) throws Throwable {
					return  proxy.invokeSuper(obj, args);
				}
			});
			
			en.create();
			System.out.println("created!");
		}

	}
}
           

2.5 直接内存溢出

package out.of.memory.test;

import java.lang.reflect.Field;

import sun.misc.Unsafe;

/**
 * 直接内存溢出:通过设置最大直接内存,并且避免堆内存扩展
 * 模拟直接内存溢出,Unsafe.allocateMemory 类似于c语言的 malloc函数分配内存
 * 
 * @author yli
 *
 */
public class DirectTest {

	private static final int _1MB = 1024 * 1024;
	
	public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
		Field f = Unsafe.class.getDeclaredFields()[0];
		f.setAccessible(true);
		Unsafe us = (Unsafe)f.get(null);
		while(true) {
			us.allocateMemory(_1MB);
			System.out.println(true);
		}
	}
		
}