起因

CXF 2.7版本和最新的3.5版本,当应用 动静调用WebService性能时,如果返回值联合应用Jackson转json性能,
那么存在 非堆内存透露问题。
详情参见:一波三折!记一次非堆内存透露(CXF+Jackson)的排查

透露起因

CXF动静调用WebService,个别蕴含以下步骤:

  1. 下载WSDL文件
  2. 将WSDL文件解析成Java代码模型
  3. 生成Java源码
  4. 编译源码->Class文件
  5. 创立ClassLoader
  6. 加载Class
  7. 创立数据绑定&类型初始化
  8. 应用这些Class实现WebService交互
  9. 废除这些Class,期待JVM闲暇时GC回收

然而,如果在对WebService返回值进行解决时,应用了Jackson的toJson性能,那么Jackson会持有CXF动静生成的Class,导致每次动静产生的Class无奈回收,Class数量一直增长,最终导致 非堆内存溢出。

解决办法

解决办法有很多种,咱们这里提供将webservice动静调用时产生的class/classloader缓存起来复用,一则解决class透露问题,二则因为缩小了每次wsdl文件解析、java代码生成、java代码编译、class加载的过程,对webservice动静调用还有一些性能上的晋升。当然,为了避免wsdl变动引起缓存变成脏数据的问题,能够通过对wsdl的MD5来判断缓存有效性。

因为不确定CXF的ClientImpl对象是否为重型对象,不确定此对象是否适合进行缓存应用、以及在并发场合下应用,革新计划的次要缓存内容为ClassLoader。

参考实现

/** * 动静调用webservice缓存内容 *  * @author sswhsz * @CreateDate 2022-11-18 */public class CacheItem {    /**     * http://*?wsdl、或者 file:/*.wsdl 目前只起备用阐明作用,不作为唯一性辨认     */    private String wsdlLocation;    /**     * wsdl文档的MD5,用于wsdl的唯一性判断     */    private String wsdlMD5;    /**     * wsdl文档的长度,疾速判断用     */    private int wsdlSize;    /**     * wsdl解析的Java模型(仅办法原型、不含实现代码)     */    private JavaModel javaModel;    private S2JJAXBModel intermediateModel;    private JAXBDataBinding databinding;    /**     * wsdl解析的定义文档 (因为不缓存Client,每次创立Client时均须要解析definition对象)     */    private Definition definition;    /**     * 已加载 webservice class的classLoader,缓存备用;     *      * 另外,为了避免此classLoader应用平台bundle作为parent,从而阻止平台bundle的卸载/更新;     *      * 动静生成的classloader将应用 零碎classloader作为parent (ClassLoader.getSystemClassLoader)     */    private ClassLoader classLoader;    //getter、setter略}
/** * 反对WebService动静调用缓存 *  * @author sswhsz * @CreateDate 2022-11-18 */public class CacheDynamicClientFactory extends DynamicClientFactory {    protected CacheDynamicClientFactory(Bus bus) {        super(bus);    }    /**     *      * @param wsdlUrl     *        wsdl本地文件门路,例如:tempFile.getAbsolutePath()     * @param cacheItem     *        缓存内容,如果是第一次创立classloader/definition,创立的内容将写入 cacheItem,否则从cacheItem读取     * @return     */    public Client createClientUseCache(String wsdlUrl, CacheItem cacheItem) {        LOG.log(Level.FINE, "Creating client from WSDL " + wsdlUrl);        Definition definition = cacheItem.getDefinition();        if (definition == null) {            definition = createDefinition(wsdlUrl);            cacheItem.setDefinition(definition);        }        Service svc = createService(definition);        ClientImpl client = new ClientImpl(bus, svc, /*port*/null, getEndpointImplFactory());        ClassLoader classLoader = cacheItem.getClassLoader();        if (classLoader == null) {            // our hashcode + timestamp ought to be enough.            String stem = toString() + "-" + System.currentTimeMillis();            File src = new File(tmpdir, stem + "-src");            File classes = new File(tmpdir, stem + "-classes");            //1、生成java代码            Object[] models = generateJavaCode(wsdlUrl, svc, /*bindingFiles*/null, src);            JCodeModel codeModel = (JCodeModel) models[0];            S2JJAXBModel intermediateModel = (S2JJAXBModel) models[1];            //2.编译            compilerJavaCode(src, classes);            //3.创立classloader            classLoader = createClassLoader(classes);            //4.创立数据绑定 & 加载class            JAXBDataBinding databinding = createDataBinding(codeModel, classLoader);            svc.setDataBinding(databinding);            //5. 类型初始化? 原始代码照搬-->没太看懂            typeClassInit(classLoader, client, intermediateModel);            // delete the classes files            FileUtils.removeDir(classes);            cacheItem.setIntermediateModel(intermediateModel);            cacheItem.setDatabinding(databinding);            cacheItem.setClassLoader(classLoader);        }        else {            JAXBDataBinding databinding = cacheItem.getDatabinding();            svc.setDataBinding(databinding);            S2JJAXBModel intermediateModel = cacheItem.getIntermediateModel();            typeClassInit(classLoader, client, intermediateModel);        }        //设置上下文ClassLoader的目标是为了后续 invoke办法时,从上下文中获取加载的class        //但此处 CXF设计不佳。 更正当的做法是 将classloader存储在ClientImpl上,在invoke办法前后 try{ 设置} finally {勾销设置}        ClassLoaderUtils.setThreadContextClassloader(classLoader);        return client;    }    /**     * java代码生成     *      * @param src     *        生成Java代码的根目录     * @return Object[] 返回元素2:[0]=JCodeModel, [1]=intermediateModel     */    protected Object[] generateJavaCode(String wsdlUrl, Service svc, List<String> bindingFiles, File src) {        //all SI's should have the same schemas        SchemaCollection schemas = svc.getServiceInfos().get(0).getXmlSchemaCollection();        SchemaCompiler compiler = JAXBUtils.createSchemaCompilerWithDefaultAllocator(new HashSet<String>());        InnerErrorListener listener = new InnerErrorListener(wsdlUrl);        Object elForRun = ReflectionInvokationHandler.createProxyWrapper(listener,            JAXBUtils.getParamClass(compiler, "setErrorListener"));        compiler.setErrorListener(elForRun);        OASISCatalogManager catalog = bus.getExtension(OASISCatalogManager.class);        hackInNewInternalizationLogic(compiler, catalog);        addSchemas(compiler.getOptions(), compiler, svc.getServiceInfos(), schemas);        addBindingFiles(bindingFiles, compiler);        S2JJAXBModel intermediateModel = compiler.bind();        listener.throwException();        JCodeModel codeModel = intermediateModel.generateCode(null, elForRun);        if (!src.mkdir()) {            throw new IllegalStateException("Unable to create working directory " + src.getPath());        }        try {            Object writer = JAXBUtils.createFileCodeWriter(src);            codeModel.build(writer);        }        catch (Exception e) {            throw new IllegalStateException("Unable to write generated Java files for schemas: " + e.getMessage(), e);        }        return new Object[] { codeModel, intermediateModel };    }    protected void compilerJavaCode(File src, File classes) {        if (!classes.mkdir()) {            throw new IllegalStateException("Unable to create working directory " + classes.getPath());        }        StringBuilder classPath = new StringBuilder();        try {            ClassLoader parent = ClassLoader.getSystemClassLoader();            setupClasspath(classPath, parent);        }        catch (Exception ex) {            throw new RuntimeException(ex);        }        List<File> srcFiles = FileUtils.getFilesRecurse(src, ".+\\.java$");        if (!compileJavaSrc(classPath.toString(), srcFiles, classes.toString())) {            LOG.log(Level.SEVERE, new Message("COULD_NOT_COMPILE_SRC", LOG, src).toString());        }        FileUtils.removeDir(src);    }    protected ClassLoader createClassLoader(File classes) {        URL[] urls = null;        try {            urls = new URL[] { classes.toURI().toURL() };        }        catch (MalformedURLException mue) {            throw new IllegalStateException("Internal error; a directory returns a malformed URL: " + mue.getMessage(),                mue);        }        //留神:parentClassLoader应用 system class loader, 防止无心中阻止 bundle class loader的卸载和更新        ClassLoader parent = ClassLoader.getSystemClassLoader();        ClassLoader cl = ClassLoaderUtils.getURLClassLoader(urls, parent);        return cl;    }    protected void typeClassInit(ClassLoader classLoader, ClientImpl client, S2JJAXBModel intermediateModel) {        //5. 应用线程上下文ClassLoader加载class        ClassLoaderHolder holder = ClassLoaderUtils.setThreadContextClassloader(classLoader);        try {            ServiceInfo svcfo = client.getEndpoint().getEndpointInfo().getService();            TypeClassInitializer visitor = new TypeClassInitializer(svcfo, intermediateModel, allowWrapperOps());            visitor.walk();        }        finally {            holder.reset();        }    }    protected JAXBDataBinding createDataBinding(JCodeModel codeModel, ClassLoader cl) {        JAXBContext context;        Map<String, Object> contextProperties = jaxbContextProperties;        if (contextProperties == null) {            contextProperties = Collections.emptyMap();        }        StringBuilder sb = new StringBuilder();        boolean firstnt = false;        for (Iterator<JPackage> packages = codeModel.packages(); packages.hasNext();) {            JPackage jpackage = packages.next();            if (!isValidPackage(jpackage)) {                continue;            }            if (firstnt) {                sb.append(':');            }            else {                firstnt = true;            }            sb.append(jpackage.name());        }        JAXBUtils.logGeneratedClassNames(LOG, codeModel);        String packageList = sb.toString();        try {            if (StringUtils.isEmpty(packageList)) {                context = JAXBContext.newInstance(new Class[0], contextProperties);            }            else {                context = JAXBContext.newInstance(packageList, cl, contextProperties);            }        }        catch (JAXBException jbe) {            throw new IllegalStateException("Unable to create JAXBContext for generated packages: " + jbe.getMessage(),                jbe);        }        JAXBDataBinding databinding = new JAXBDataBinding();        databinding.setContext(context);        return databinding;    }    protected Definition createDefinition(String wsdlUrl) {        try {            // use wsdl manager to parse wsdl or get cached definition            Definition definition = bus.getExtension(WSDLManager.class).getDefinition(wsdlUrl);            return definition;        }        catch (WSDLException ex) {            throw new ServiceConstructionException(new Message("SERVICE_CREATION_MSG", LOG), ex);        }    }    protected Service createService(Definition definition) {        WSDLServiceFactory sf = new WSDLServiceFactory(bus, definition);        sf.setAllowElementRefs(allowRefs);        Service svc = sf.create();        return svc;    }    public static CacheDynamicClientFactory newInstance() {        Bus bus = CXFBusFactory.getThreadDefaultBus();        return new CacheDynamicClientFactory(bus);    }}