WAS上部署Jmx应用中包冲突解决过程
近日,在was6.0上面部署基于JMX的一个应用,出现一个JMX类的JMXConnectorFactory.resolveClassLoader()的MethodNotFound的错误,但是明明JMX的类也就是jmx-remote.jar包里面的类有该类的方法,如图:
在开发机器上的tomcat上跑没有错,为什么布署在was上就有问题,肯定是环境出了问题,幸好有点was部署经验,出现此类java ERROR级别的错误,绝大多数是jar包冲突的的问题,于是打开was控制台,利用was的类装入器查看器发现在was的一个名为management.jar包里面也有同为JMXConnectorFactory的类,于是抓下来一看究竟。
果然不出所料,没有resolveClassLoader方法,然而management的类加载层次肯定是比jmx-remote.jar的层次高,默认的类加载策略是先加载父类,理说当然,jmx-remote.jar包中的类不可能被加载。
说起为什么冲突,还得说说历史。Jmx在jdk1.4时代是作为sun的一个扩展包存在的,并不是标准的jdk的包,于是乎ibm也有他的jmx的实现,而且包名和sun的一样,要是不一样就不会有冲突了。
好吧,能不能不使用基于JMX的包里面的实现,用management里面的实现呢,结果很失望,management里RmiConnectorServer根本没有实现。自己照着jmx-remote.jar里面的实现重写?实践了才发现不可能,重写一个得重写几十个类,还不能保证运行正常。
由于jmx的jar包在was上面部署的时候是采用共享包的方式部署的,我首先考虑的是能否考虑这个共享库的加载方式,改成后加载父类。为了便于理解
我们假设java的boot classloader叫做A,was的系统jar包classloader叫做B,
共享库的classloader加载C,应用程序的classLoader叫做D。他们的层次如图:
其中C加载器加载jmx-remote.jar,如果能改变C的加载策略,改变为后加载父类,那么就可以避免加载management里面的类了。
解决方案有两种:
还有一种可能想到的解决方案,就是能不能在A和B之间有一个classload E,让E去加载jmx相关的类。这样要找的肯定是E加载器中的jmx相关的类了。理想是好了,而且我们确实可以把E加载器放在A和B之间。
可以设置-Djava.ext.dirs变量或者设置-Djava.class.path变量,多个目录用”:”隔开,这种设置的前提是要知道变量的值是多少,否则就破坏系统的设置了。
一切看起来完美,布署运行,出现以下错误:
Caused by: java.lang.LinkageError: Class javax/management/remote/JMXServiceURL violates loader constraints: parent and child already loaded different classes
at java.lang.ClassLoader.defineClass0(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java(Compiled Code))
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java(Compiled Code))
at java.net.URLClassLoader.defineClass(URLClassLoader.java(Compiled Code))
at java.net.URLClassLoader.access$500(URLClassLoader.java(Inlined Compiled Code))
at java.net.URLClassLoader$ClassFinder.run(URLClassLoader.java(Compiled Code))
at java.security.AccessController.doPrivileged1(Native Method)
at java.security.AccessController.doPrivileged(AccessController.java(Compiled Code))
at java.net.URLClassLoader.findClass(URLClassLoader.java(Compiled Code))
at java.lang.ClassLoader.loadClass(ClassLoader.java(Compiled Code))
at java.lang.ClassLoader.loadClass(ClassLoader.java(Compiled Code))
at javax.management.remote.JMXConnectorServerFactory.newJMXConnectorServer(JMXConnectorServerFactory.java:275)
灰常底层的错误啊!google,baidu之。原因是这样的
Classloader D 调用一个方法JMXConnectorServerFactory. newJMXConnectorServer(JMXServiceURL serviceURL,Map environment,
MBeanServer mbeanServer)来新建一个ConnectorServer
这个方法可以从spring的源码中看到。先看看目前的类加载器:
D会去找JMXConnectorServerFactory类,由于B中没有所以找到了E中的JMXConnectorServerFactory,同时也会去找JMXServiceURL,找到B的时候,这里是关键,由于Classloader B (WAS引导程序扩展类装入器)出于保护was类的原因,其策略是后加载父类(parent_last),所以找到了B中的JMXServiceURL。然而实际上,JMXConnectorServerFactory.newJMXConnectorServer方法需要的是E中的JMXServiceURL类,并且E中的JMXServiceURL和B中的JMXServiceURL字节码不一样,所以会出现violates loader constraints: parent and child already loaded different classes这种错误,也就是类装入器约束违反。
如果还不清楚的话,可以参考:
既然绕不过B,还是只能有上述两种解决方案了。两种比较而言方案二更安全点,毕竟jvm上面跑的不只是这一个应用,动配置的事情能影响小就影响小点吧。