Java内存区域之方法区溢出

方法区溢出

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:-UseGCOverheadLimitGC支出超标限制(超过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)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值