方法区溢出
在Java内存区域的Java虚拟机栈
章节介绍过,方法区主要存储类型相关信息
,在JDK 6及以前版本字符串常量池
也在此区域存储,JDK 7时将字符串常量池移入堆内存。
本文主要将分别介绍由字符串常量池
、类型相关信息
导致内存溢出的情况。
字符串常量池
我们可以通过String::intern()
将字符串存入到字符串常量池中,如下示例代码不断调用String::intern()方法使字符串常量池不断增大,导致内存溢出。
package jvm.oom;
import java.util.ArrayList;
import java.util.List;
/**
* 通过不断调用String::intern()方法使字符串常量池不断增大,导致内存溢出。
* 虚拟机参数:
* JDK 6: -Xmx50m -XX:MaxPermSize=10m
* JDK 8: -Xmx50m -XX:MaxMetaspaceSize=10m -XX:-UseGCOverheadLimit
*
* @author faith.huan 2020-01-10 21:34
*/
public class JavaMethodAreaStrOom {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
int strCount = 0;
while (true) {
list.add(String.valueOf(strCount++).intern());
}
}
}
上面代码在JDK6和JDK8上会有不同结果
-
JDK 6 - 永久代溢出
使用虚拟机参数:-Xmx50m -XX:MaxPermSize=10m参数 解释 -Xmx50m 堆上限设置为50m -XX:MaxPermSize=10m 永久代上限10m 执行结果
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space at java.lang.String.intern(Native Method) at jvm.oom.JavaMethodAreaStrOom.main(JavaMethodAreaStrOom.java:20)
-
JDK 8 -堆内存溢出
使用虚拟机参数:-Xmx50m -XX:MaxMetaspaceSize=10m -XX:-UseGCOverheadLimit参数 解释 -Xmx50m 堆上限设置为50m -XX:MaxMetaspaceSize=10m 元空间上限10m -XX:-UseGCOverheadLimit GC支出超标限制(超过98%的时间用来做GC并且回收了不到2%的堆内存) , -XX:-UseGCOverheadLimit
表示不限制也就是当用了98%的时间来进行垃圾回收却收集了不到2%的堆内存时不抛出java.lang.OutOfMemoryError: GC overhead limit exceeded
异常
执行结果为堆内存溢出
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.lang.Integer.toString(Integer.java:403)
at java.lang.String.valueOf(String.java:3099)
at jvm.oom.JavaMethodAreaStrOom.main(JavaMethodAreaStrOom.java:20)
类型相关信息
可以通过产生大量类来填满方法区,使其内存溢出。产生大量类有有多种方式,例如JDK提供的动态代理,第三方的CGLib。
本部分测试都基于JDK 8
进行测试
java version "1.8.0_201"
Java(TM) SE Runtime Environment (build 1.8.0_201-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)
动态代理
示例代码
package jvm.oom;
import proxy.JdkProxy;
import sun.misc.ProxyGenerator;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* 通过模拟JDK动态代理,实现将方法区填满
* 虚拟机参数 -XX:MaxMetaspaceSize=10m
*
* @author faith.huan 2020-01-12 13:00
*/
public class JavaMethodAreaProxyOom {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException {
// 通过反射获取Proxy类Class对象
Class<?> aClass = Class.forName("java.lang.reflect.Proxy");
// 通过反射获取defineClass0方法,并设置访问权限(默认为私有,无法直接访问)
Method defineClass0Method = aClass.getDeclaredMethod("defineClass0", ClassLoader.class, String.class,
byte[].class, int.class, int.class);
defineClass0Method.setAccessible(true);
// 设置动态代理的接口
Class[] interfaces = new Class[]{Serializable.class};
// 生成代理class的数量,如果用10m元空间测试,1万个足够让其内存溢出
int generateClassNumber = 10000;
for (int i = 0; i < generateClassNumber; i++) {
// 代理类名
String proxyName = "$Proxy" + i;
// 生成代理类的二进制流
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, Modifier.PUBLIC);
// 加载类进虚拟机
try {
defineClass0Method.invoke(new Object(), JdkProxy.class.getClassLoader(), proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (Throwable e) {
System.out.println(proxyName);
throw e;
}
}
}
}
运行时使用-XX:MaxMetaspaceSize=10m
将元空间上限设置为10m(默认值为-1,不限制),运行结果如下:
$Proxy2209
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at jvm.oom.JavaMethodAreaProxyOom.main(JavaMethodAreaProxyOom.java:39)
Caused by: java.lang.OutOfMemoryError: Metaspace
at java.lang.reflect.Proxy.defineClass0(Native Method)
... 4 more
CGLib
package jvm.oom;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 使用CGLib产生大量类,模拟内存溢出
* 虚拟机参数 -XX:MaxMetaspaceSize=10m
*
* @author faith.huan 2020-01-12 11:06
*/
public class JavaMethodAreaCglibOom {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(User.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(object, args);
}
});
enhancer.create();
}
}
static class User {
}
}
运行时使用-XX:MaxMetaspaceSize=10m
将元空间上限设置为10m(默认值为-1,不限制),运行结果如下:
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:348)
at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)
at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:117)
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:294)
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:305)
at jvm.oom.JavaMethodAreaCglibOom.main(JavaMethodAreaCglibOom.java:28)