罕见类加载冲突问题:LinkageError
2016-06-02 18:50
309 查看
问题描述
假设有C1类和C2类都依赖C0,C1和C2分别用不同的2个类加载器加载,而这两个类加载器都能在自己的类加载路径中加载到C0,这个时候如果在C1中调用C2的某个方法(注:这个方法的签名中依赖了C0)就会出现LinkageError错误。用例模拟及分析
冲突的依赖类,模拟问题描述中的C0package loader; public class ConflictDependence { }
发生错误的类,模拟问题描述中的C2
package loader; public class ErrorTester { static { // 在其它ClassLoader中必需使用一次这个 MyDependence 依赖, // 这样在另一个ClassLoader使用的时候就会出错 System.out.println("ErrorTester MyDependence Location: " + ConflictDependence.class.getProtectionDomain().getCodeSource()); } // 不会出错的方法 // public static void test0() { // System.out.println("ErrorTester MyDependence Location: " + // ConflictDependence.class.getProtectionDomain().getCodeSource()); // } // 出错方法 // public static void test(ConflictDependence a) { // } // 出错方法 public static ConflictDependence test() { return null; } }
被另一个类加载器加载的类,模拟问题描述中的C1
package loader; public class SubClass { private ConflictDependence myDependence; public SubClass() { System.out.println(ConflictDependence.class.getProtectionDomain().getCodeSource()); ConflictDependence test = ErrorTester.test(); } }
测试类
package loader; import java.io.File; import java.net.URL; import java.net.URLClassLoader; public class Test { public static void main(String[] args) throws Exception { URL[] urls = new URL[]{new File("/home/conquer/Desktop/jse.jar").toURI().toURL()}; SubFirstLoader newLoader = new SubFirstLoader(urls); ErrorTester.test(); Class<?> b = newLoader.loadClass("loader.SubClass"); b.newInstance(); } static class SubFirstLoader extends URLClassLoader { public SubFirstLoader(URL[] urls) { super(urls); } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { // 从自身类路径中加载 return this.findClass(name); } catch (Exception e) { // 从父ClassLoader加载 return super.loadClass(name); } } } }
模拟说明:
1.将以上的4个类打jar包,名为为jse.jar或其他名字
2.从jar中删除ErrorTester和Test两个class文件
3.修改测试程序Test中的jar路径,运行测试即可看到出错信息
问题分析:
测试中使用了两个类加载器来加载类,使用系统类加载器加载ErrorTester,这个类依赖了ConflictDependence,可以看到静态块里同时也打印出系统类加载器也从自己的加载路径中加载了ConflictDependence,然后使用自定义的类加载器(一个子优先的类加载器)加载了Sub Class,并通过反射构造其实例,构造过程中依赖了ConflictDependence,所以会去尝试加载ConflictDependence,由于自定义的加载器是子优先模式的,会先从自身类加载路径查找并加载,所以可以加载到一个ConflictDependence,注意这个ConflictDependence和系统类加载器加载的并不是同一个,在SubClass中调用了ErrorTester.test()方法,这个方法的签名依赖的是系统类加载器加载的ConflictDependence,而SubClass也有同名的不同实例的ConflictDependence,因此就产生了冲突,这里表现为LinkageError,就是SubClass将自己加载的ConflictDependence链接到ErrorTester.test()方法(这个方法的签名在当前内存中依赖的是系统类加载器加载的ConflictDependence)时产生错误。
继续测试发现,调用test0()方法并无错误,这是因为test0()方法的签名没有依赖到冲突的
ConflictDependence(这里需要注意方法体内的ConflictDependence并不会产生冲突错误,这是因为方法体的字节码不会编译到SubClass的字节码中,类加载器约束检查只会在当前这个SubClass类的方法中检查冲突,其他方法体的冲突是在其他方法体内检查的,简单说就是类冲突检查的单元是在一个方法体内),
另外需要说明的是方法的参数和方法的返回值都属于方法的签名,方法的签名都会编译到调用端的方法体内,所以上面的void test(ConflictDependence a)方法和ConflictDependence test()都会出错。
另外需要注意的是:ErrorTester中使用了静态块一个是可以看出当前实例加载的是哪个ConflictDependence,另一个原因是为了触发一下让系统类加载加载一下ConflictDependence,以促成后面的冲突问题产生,其实如果不在静态块中触发,在test()的方法体中触发也是可以产生此问题的,同理SubClass中的打印也是为了触发当前类的类加载器加载一下ConflictDependence。
关于更多类加载问题,请参考:
ClassLoader问题剖析
相关文章推荐
- 如何解决双网卡冲突
- 两个打印机服务spoolsv.exe存在冲突的解决方法
- setAttribute 与 class冲突解决
- 实例讲解避免javascript冲突的方法
- jQuery与其它库冲突的解决方法
- jQuery prototype冲突的2种解决方法(附demo示例下载)
- 解决jquery版本冲突的有效方法
- 快速解决jquery.touchSwipe左右滑动和垂直滚动条冲突
- 快速解决jQuery与其他库冲突的方法介绍
- jquery单击事件和双击事件冲突解决方案
- 加载jQuery后$冲突的解决办法
- 关于svn冲突的解决方法
- 避免Smarty与CSS语法冲突的方法
- 数据转换冲突及转换过程中大对象的处理
- PHP针对常规模板引擎中与CSS/JSON冲突的解决方法
- ThinkPHP的cookie和session冲突造成Cookie不能使用的解决方法
- asp.net开发与web标准的冲突问题的一些常见解决方法
- onclick和onblur冲突问题的快速解决方法
- transport.js和jquery冲突问题的解决方法
- Win8.1系统程序运行发生冲突提示"APPCRASH”错误的故障原因及解决方法