起因
CXF 2.7版本和最新的3.5版本,当使用 动态调用WebService功能时,如果返回值结合使用Jackson转json功能,
那么存在 非堆内存泄漏问题。
详情参见:一波三折!记一次非堆内存泄漏(CXF+Jackson)的排查
泄漏原因
CXF动态调用WebService,一般包含以下步骤:
- 下载WSDL文件
- 将WSDL文件解析成Java代码模型
- 生成Java源码
- 编译源码->Class文件
- 创建ClassLoader
- 加载Class
- 创建数据绑定&类型初始化
- 使用这些Class完成WebService交互
- 废弃这些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);
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。