CXF导致的JVM MetaSpace OOM问题

线上CXF版本: 3.1.12
使用场景: 定时任务,每次任务启动时调用jaxWsDynamicClientFactory.createClient(wsdlUrl)创建客户端client,每次任务结束前调用client.destroy()销毁客户端
问题: 任务运行一段时间后,metaspace溢出导致服务重启

分析dump文件: 发现程序创建了大量的URLClassLoader,链式引用,无法回收,最后导致MetaSpace溢出
在这里插入图片描述
CXF源码分析:
jaxWsDynamicClientFactory.createClient(wsdlUrl)最终会走如下方法

public Client createClient(String wsdlUrl, QName service, ClassLoader classLoader, QName port, List<String> bindingFiles) {
		//传入的classLoader为null,因此获取当前线程上下文类加载器
        if (classLoader == null) {
            classLoader = Thread.currentThread().getContextClassLoader();
        }

        LOG.log(Level.FINE, "Creating client from WSDL " + wsdlUrl);
        WSDLServiceFactory sf = service == null ? new WSDLServiceFactory(this.bus, wsdlUrl) : new WSDLServiceFactory(this.bus, wsdlUrl, service);
        sf.setAllowElementRefs(this.allowRefs);
        Service svc = sf.create();
        SchemaCollection schemas = ((ServiceInfo)svc.getServiceInfos().get(0)).getXmlSchemaCollection();
        SchemaCompiler compiler = this.createSchemaCompiler();
        DynamicClientFactory.InnerErrorListener listener = new DynamicClientFactory.InnerErrorListener(wsdlUrl);
        Object elForRun = ReflectionInvokationHandler.createProxyWrapper(listener, JAXBUtils.getParamClass(compiler, "setErrorListener"));
        compiler.setErrorListener(elForRun);
        OASISCatalogManager catalog = (OASISCatalogManager)this.bus.getExtension(OASISCatalogManager.class);
        this.hackInNewInternalizationLogic(compiler, catalog);
        this.addSchemas(compiler.getOptions(), compiler, svc.getServiceInfos(), schemas);
        this.addBindingFiles(bindingFiles, compiler);
        S2JJAXBModel intermediateModel = compiler.bind();
        listener.throwException();
        JCodeModel codeModel = intermediateModel.generateCode((Object)null, elForRun);
        StringBuilder sb = new StringBuilder();
        boolean firstnt = false;
        Iterator packages = codeModel.packages();

        while(packages.hasNext()) {
            JPackage jpackage = (JPackage)packages.next();
            if (this.isValidPackage(jpackage)) {
                if (firstnt) {
                    sb.append(':');
                } else {
                    firstnt = true;
                }

                sb.append(jpackage.name());
            }
        }

        JAXBUtils.logGeneratedClassNames(LOG, codeModel);
        String packageList = sb.toString();
        String stem = this.toString() + "-" + System.currentTimeMillis();
        File src = new File(this.tmpdir, stem + "-src");
        if (!src.mkdir()) {
            throw new IllegalStateException("Unable to create working directory " + src.getPath());
        } else {
            try {
                Object writer = JAXBUtils.createFileCodeWriter(src);
                codeModel.build(writer);
            } catch (Exception var34) {
                throw new IllegalStateException("Unable to write generated Java files for schemas: " + var34.getMessage(), var34);
            }

            File classes = new File(this.tmpdir, stem + "-classes");
            if (!classes.mkdir()) {
                throw new IllegalStateException("Unable to create working directory " + classes.getPath());
            } else {
                StringBuilder classPath = new StringBuilder();

                try {
                    setupClasspath(classPath, classLoader);
                } catch (Exception var33) {
                    throw new RuntimeException(var33);
                }

                List<File> srcFiles = FileUtils.getFilesRecurse(src, ".+\\.java$");
                if (srcFiles.size() > 0 && !this.compileJavaSrc(classPath.toString(), srcFiles, classes.toString())) {
                    LOG.log(Level.SEVERE, (new Message("COULD_NOT_COMPILE_SRC", LOG, new Object[]{wsdlUrl})).toString());
                }

                FileUtils.removeDir(src);
                URL[] urls = null;

                try {
                    urls = new URL[]{classes.toURI().toURL()};
                } catch (MalformedURLException var32) {
                    throw new IllegalStateException("Internal error; a directory returns a malformed URL: " + var32.getMessage(), var32);
                }
				 //该方法会new一个类加载器,并把传入的线程上下文类加载器作为父加载器,
                ClassLoader cl = ClassLoaderUtils.getURLClassLoader(urls, classLoader);
                Map<String, Object> contextProperties = this.jaxbContextProperties;
                if (contextProperties == null) {
                    contextProperties = Collections.emptyMap();
                }

                JAXBContext context;
                try {
                    if (StringUtils.isEmpty(packageList)) {
                        context = JAXBContext.newInstance(new Class[0], contextProperties);
                    } else {
                        context = JAXBContext.newInstance(packageList, cl, contextProperties);
                    }
                } catch (JAXBException var31) {
                    throw new IllegalStateException("Unable to create JAXBContext for generated packages: " + var31.getMessage(), var31);
                }

                JAXBDataBinding databinding = new JAXBDataBinding();
                databinding.setContext(context);
                svc.setDataBinding(databinding);
                ClientImpl client = new DynamicClientFactory.DynamicClientImpl(this.bus, svc, port, this.getEndpointImplFactory(), cl);
                ServiceInfo svcfo = client.getEndpoint().getEndpointInfo().getService();
                //把new的类加载器设置为当前线程上下文类加载器,因为定时任务线程一直存活,因此当前线程类加载器也一直存活,这也解释了为何dump文件中会出现类加载器链式引用
                ClassLoaderUtils.setThreadContextClassloader(cl);
                TypeClassInitializer visitor = new TypeClassInitializer(svcfo, intermediateModel, this.allowWrapperOps());
                visitor.walk();
                FileUtils.removeDir(classes);
                return client;
            }
        }
    }

ClassLoaderUtils.getURLClassLoader(urls, classLoader)方法如下

    //该方法会new一个类加载器,并把传入的线程上下文类加载器作为父加载器,
    public static ClassLoader getURLClassLoader(final URL[] urls, final ClassLoader parent) {
        return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
            public ClassLoader run() {
            	//此处new了一个ClassLoader
                return new URLClassLoader(urls, parent);
            }
        });
    }

CXF bug描述: https://issues.apache.org/jira/browse/CXF-6852
虽然issue中说在3.1.11, 3.2.0版本已修复,但在3.1.12版本中,重复创建client时仍然会因为不断创建ClassLoader而导致MetaSpace溢出。
解决方案:
改为单例模式,不用每次定时任务启动都创建client

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值