具体场景
最近项目中基于jarslink做实时算法的加载,为了方便jar包更新测试,我们把jar包开发完后以snapshot版本推送到maven中央仓库中。jarslink查询器根据配置动态从maven仓库加载算法jar包及其依赖。为了在jar包发生改变时动态刷新,代码中农将对应jar包的md5作为版本version的一部分。在代码发生修改时,重新推送jar包到maven仓库,代码中的maven加载器动态刷新插件jar包,并重新加载子容器,此时意外发生了,jvm crash,多次试验,java虚拟机不是crash就是报ClassNotFoundException,原因为何?这就是本文要说的:不要在java虚拟机运行时替换正在使用的jar包!
具体原因探索
既然jar包名称和原来的一模一样,jar包里面的类也一致,为什么jvm就加载不到这个jar包里面的类呢?要回答这个问题,我们可以先通过lsof来查看一下某个java进程打开的文件句柄:
20190322225128453.pnglsof | grep java 6213
我们可以看到,这个java进程维护了多个jar包文件的句柄。通过这个,我们可以想到应该是jvm在运行中维护了这些jar包的inputstream,在需要某个类的时候,就从这些inputstream中获取某个类的字节码然后加载。这个维护的inputstream是针对原先旧jar包打开的输入流,如果这时jar包被替换了,那么对应的句柄就会失效,ClassPathXmlApplication在去加载这个jar包并初始化容器便会报错了!
另外我还发现,你把这个jar包删除,但是拷贝一模一样的jar包回去,代码不会出现任何问题,jvm对应的jar包是mem内存映射的方式打开对应的jar包文件的,只要文件内容完全相同(字节数没变),那么就不会出现类找不好或者非法内存访问造成jvm崩溃。
总结
其实,如果jvm程序后面在运行时不需要再从这个jar包加载什么类的话,就算替换了jar包也不会造成什么影响。但是应该没有哪个程序员敢说对要维护的java服务是了如指掌的,加上jvm是按需加载的。我们很难断定jvm在运行过程中就不会再从我们要替换的这个jar包中加载类了。因此,再做jarlink插件加载时,最好不同的子容器之间不要共用同一个jar包,免得一个子模块刷新时,对应的jar包发生SNAPSHOT版本更新,造成后面代码运行崩溃。线上最好使用release版本,并且将jar包拷贝到不同子模块目录下,做jar包之间的物理隔离。
网友评论