记一次元空间内存溢出问题

发现问题

在工作中遇到了一个元空间内存溢出问题,问题出在一个用户输入Java文件,后台负责编译并执行Java文件的功能上,因为用户能随时对Java文件进行修改,所以我们每次执行这个文件的时候都会重新编译,new URLClassLoader来加载这个类,这样的话每次都是加载最新的Class,如果用同一个ClassLoader对象去加载同一个类,是不会重复去加载的。每调用一次这个执行接口,元空间就会增加一个class对象,随着调用次数增多,元空间就慢慢被沾满,这些Class对象却不能被卸载掉,为啥呢?按说Class对象只要满足3个条件就能被卸载:

  1. Class对象的所有实例都没有直接引用
  2. 加载class的ClassLoader也没有直接引用
  3. class对象没有被任何地方引用

寻找原因

看条件好像都符合,没有办法就写个简单的类测试一下。

public class UrlClassLoaderTest {
    public static void main(String[] args) throws Exception {
        while (true) {
            loadClass();
        }
    }

    private static void loadClass() throws Exception {

        URLClassLoader loader = new URLClassLoader(new URL[]{ new File("E://test/").toURI().toURL()}, Thread.currentThread().getContextClassLoader());
        Class aClass = loader.loadClass("com.example.demo.TestImpl");
        TestInterface testInterface = (TestInterface) aClass.newInstance();
        testInterface.call(null);
    }
}

执行的时候加上参数:

-verbose:class -XX:MaxMetaspaceSize=30M

执行一段时间可以看到会去卸载类:
在这里插入图片描述
思路没有问题,继续根据接源代码完善测试类, 里面一共有加载三个类,一个入参,一个出参,一个service,接口传进来json,用fastjson转换成入参对象,模拟一下:

public class UrlClassLoaderTest {
    public static void main(String[] args) throws Exception {
        while (true) {
            loadClass();
        }
    }

    private static void loadClass() throws Exception {

        URLClassLoader loader = new URLClassLoader(new URL[]{new File("E://test/").toURI().toURL()}, Thread.currentThread().getContextClassLoader());
        Class aClass = loader.loadClass("com.example.demo.TestImpl");
        Class bClass = loader.loadClass("com.example.demo.TestParam");
        TestInterface testInterface = (TestInterface) aClass.newInstance();
        JSONObject jsonObject = new JSONObject();
        testInterface.call(jsonObject.toJavaObject(bClass));
    }
}

这个时候再去执行,发现没多久就内存溢出了:
在这里插入图片描述
问题就出在jsonObject.toJavaObject方法上,进源代码查看,发现这个方法里面居然把Class对象保存起来了,toJavaObject这个里面调用了TypeUtils.castToJavaBean,传了 ParserConfig.getGlobalInstance()这个全局对象

    public <T> T toJavaObject(Class<T> clazz) {
        if (clazz == Map.class || clazz == JSONObject.class || clazz == JSON.class) {
            return (T) this;
        }

        if (clazz == Object.class && !containsKey(JSON.DEFAULT_TYPE_KEY)) {
            return (T) this;
        }

        return TypeUtils.castToJavaBean(this, clazz, ParserConfig.getGlobalInstance());
    }

然后TypeUtils.castToJavaBean这个方法里面有调用config.get(clazz),先根据class去获取ObjectDeserializer,如果不存在就会新创建,然后把class作为key保存到config的map里面,这样就造成了类的卸载不符合第3个条件。

ObjectDeserializer deserializer = config.get(clazz);
if(deserializer != null){
    String json = JSON.toJSONString(object);
    return JSON.parseObject(json, clazz);
}

解决办法

调用jsonObject.toJavaObject(bClass,new ParserConfig(),1)这个方法,这样用的就是私有的config,随着方法的结束,这个config也再没有引用,可以被回收。另外Object转json也有缓存class对象,如果想要class对象不被缓存,调用JSONObject.toJSONString(new Object(), serializeConfig);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值