了解了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。
-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();
}
}
}