需求背景
用户将自己写的java代码打成jar包,然后在A项目的前端页面上上传,后台会将jar包通过http方式上传到B项目。
现在有个需求是:A项目某业务逻辑会用到B项目的jar包,执行其中的方法并拿到方法返回结果。
需求的难点在于:
A项目如何远程执行B项目中jar包中的方法呢?
URLClassLoader远程加载
java中提供了URLClassLoader
类来加载外部jar,并可以执行其中的方法,
参考 记录——JAVA动态加载外部JAR,并调用方法以及卸载关闭打开的外部JAR。
这篇博文介绍的是如何加载本地jar,而我们的jar是放在远程的,不过URLClassLoader
也可以直接加载远程jar,参考 URLClassLoader加载远程jar包。
于是A项目采用的是 URLClassLoader加载远程jar包的方式。
URLClassLoader的内存溢出陷阱
A项目的并发量很高,每条数据过来都要去执行jar的方法。
jar包如果很小,性能可能不受太大影响。
jar包本身就很大时,远程读取的速度会更慢,性能会很感人。
所以A项目启动时,会提前将通过URLClassLoader
加载的Class
对象缓存在内存中,后续业务处理直接从内存中取出Class
对象。
伪代码如下
Class clz = urlClassLoader.loadClass("com.sf.iotp.util.ParseUtil");
map.put(jarName, clz);
但是这样有个限制,我们不能通过
urlClassLoader.close()
来关闭外部jar,否则即使将Class
对象缓存在内存中,执行时依旧会报找不到类的异常。
通过上述做法性能是快了,但是由于没有通过urlClassLoader.close()
来关闭外部jar,可能存在内存溢出的陷阱(目前我没遇到过,但是存在这种可能性)。具体参考这篇博文 Java动态编译优化——URLClassLoader 内存泄漏问题解决
折中方案
既想通过urlClassLoader.close()
来关闭外部jar从而防止内存溢出,又想提升读取性能,该怎么做呢?
我们最后用的一个方案是:
项目启动时,提前下载所有远程jar到本地目录(服务器的目录);
当数据过来时,后台通过URLClassLoader
本地加载的方式来加载下载好的jar,此时就可以使用urlClassLoader.close()
。本地加载速度远远大于远程加载速度;
下次项目再启动时,如果服务器本地目录有jar,说明之前下载过,此时无需重新下载。
这种方案,第一次启动时由于要下载,所以会慢点,但后续就快多了。