JVM内存溢出的方式

了解了Java虚拟机五个内存区域的作用后,下面我们来继续学习下在什么情况下

这些区域会发生溢出。

1.虚拟机参数配置

-Xms:初始堆大小,默认为物理内存的1/64(<1GB);默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制。

-Xmx:最大堆大小,默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。

-Xss:每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。应根据应用的线程所需内存大小进行适当调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。一般小的应用, 如果栈不是很深, 应该是128k够用的,大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。

-XX:PermSize:设置永久代(perm gen)初始值。默认值为物理内存的1/64。

-XX:MaxPermSize:设置持久代最大值。物理内存的1/4。


2.方法区溢出

因为方法区是保存类的相关信息的,所以当我们加载过多的类时就会导致方法区
溢出。在这里我们通过JDK动态代理和CGLIB代理两种方式来试图使方法区溢出。

2.1 JDK动态代理

package com.cdai.jvm.overflow;

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

public class MethodAreaOverflow {

	static interface OOMInterface {
	}
	
	static class OOMObject implements OOMInterface {
	}
	
	static class OOMObject2 implements OOMInterface {
	}
	
	public static void main(String[] args) {
		final OOMObject object = new OOMObject();
		while (true) {
			OOMInterface proxy = (OOMInterface) Proxy.newProxyInstance(
					Thread.currentThread().getContextClassLoader(), 
					OOMObject.class.getInterfaces(), 
					new InvocationHandler() {
						@Override
						public Object invoke(Object proxy, Method method, Object[] args)
								throws Throwable {
							System.out.println("Interceptor1 is working");
							return method.invoke(object, args);
						}
					}
			);
			System.out.println(proxy.getClass());
			System.out.println("Proxy1: " + proxy);
			
			OOMInterface proxy2 = (OOMInterface) Proxy.newProxyInstance(
					Thread.currentThread().getContextClassLoader(), 
					OOMObject.class.getInterfaces(), 
					new InvocationHandler() {
						@Override
						public Object invoke(Object proxy, Method method, Object[] args)
								throws Throwable {
							System.out.println("Interceptor2 is working");
							return method.invoke(object, args);
						}
					}
			);
			System.out.println(proxy2.getClass());
			System.out.println("Proxy2: " + proxy2);
		}
	}

}
虽然我们不断调用Proxy.newInstance()方法来创建代理类,但是JVM并没有内存溢出。
每次调用都生成了不同的代理类实例,但是代理类的Class对象没有改变。是不是Proxy
类对代理类的Class对象有缓存?具体原因会在之后的《JDK动态代理与CGLIB》中进行
详细分析。

2.2 CGLIB代理

CGLIB同样会缓存代理类的Class对象,但是我们可以通过配置让它不缓存Class对象,
这样就可以通过反复创建代理类达到使方法区溢出的目的。
package com.cdai.jvm.overflow;

import java.lang.reflect.Method;

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

public class MethodAreaOverflow2 {

	static class OOMObject {
	}

	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 method.invoke(obj, args);
				}
			});
			OOMObject proxy = (OOMObject) enhancer.create();
			System.out.println(proxy.getClass());
		}
	}
	
}


3.堆溢出

堆溢出比较简单,只需通过创建一个大数组对象来申请一块比较大的内存,就可以使
堆发生溢出。
package com.cdai.jvm.overflow;

public class HeapOverflow {

	private static final int MB = 1024 * 1024;
	
	@SuppressWarnings("unused")
	public static void main(String[] args) {
		byte[] bigMemory = new byte[1024 * MB];
	}

}


4.栈溢出

栈溢出也比较常见,有时我们编写的递归调用没有正确的终止条件时,就会使方法不断
递归,栈的深度不断增大,最终发生栈溢出。
package com.cdai.jvm.overflow;

public class StackOverflow {

	private static int stackDepth = 1;
	
	public static void stackOverflow() {
		stackDepth++;
		stackOverflow();
	}
	
	public static void main(String[] args) {
		try {
			stackOverflow();
		} 
		catch (Exception e) {
			System.err.println("Stack depth: " + stackDepth);
			e.printStackTrace();
		}
	}
	
}

posted on 2012-08-29 16:18  毛小娃  阅读(152)  评论(0编辑  收藏  举报

导航