同一个java类由不同的classloader加载问题
2013-01-12 15:30
411 查看
最近在测试项目代码中遇到同一个类由不同的classloader加载后出现的问题:
类A中有一个字段a,它的类型为X
类B中有一个字段b,它的类型也为X
类A由classLoaderA所加载,类B由classLoaderB所加载
执行赋值语句A.a = B.b,由于这两个类型均为X,可以执行,但是有一个要求,这个要求就是在A中所装载类X的装载器必须和在B中装载类X的装载器相同,否则赋值语句失败
为什么会产生上面的输出,我们可以来看一个以下的代码
首先是一个简单的类调用:
类Foo3
在上面的代码中,变量foo2引用了类Foo4的一个静态引用:
类Foo是一个非常简单的java类,即普通的java类:
重点在于如何运行这段代码,我们运行一段代码,分别使用两个类加载器来加载同一个类类Foo,运行代码如下
在上面的代码中,采用自定义的classLoader来定义类Foo3,我们来看具体的定义:
其实就是将类类Foo和类Foo3交由classLoader3即我们自定义加载器来加载,其它的类仍交由super即appClassLoader来加载。现在运行这段代码,即会有一个出错信息,出错信息如下:
错误在第7行,即Foo foo2 = Foo4.foo;这一行出错了。
为什么会出错,我来看来第一行代码:Class<?> clazz = Foo.class;这段代码,会对Foo类进行加载,采用的加载器为myClassLoader3,即加载Foo3类时所使用的加载器。这句话运行之后,即表示类Foo已经被加载了,且加载器为myClassLoader3。
第二行代码:Foo foo2 = Foo4.foo。这段代码会初始化Foo4,由于myClassLoader3并没有特殊处理Foo4,所以将由父类加载器,即AppClassLoader来加载,在加载过程中,因为调用到了Foo4.foo,所以会加载Foo类。这个加载是在Foo4类初始化时进行加载的。因为在碰到类Foo时,appClassLoader显示其从未加载过foo(先前的foo是由myClassLoader加载的,而不是由appClassLoader加载的),所以又会加载Foo。
这时候,类Foo就会有两个类加载器,一个是由myClassLoader3加载的,另一个是由appClassLoader加载的。如果两个类分开运行,代码是没有问题的。
问题就出在这个赋值语句,或者说是对象引用上。在Foo3内部使用Foo4.foo时,JVM会记录Foo4.foo在foo3内部的类引用和加载器,在这个运行代码中,此加载器为myClassLoader,因为在调用Foo4.foo之前已经加载了Foo。然而,在引用时,它将得到声明Foo4.foo时的Foo类型的加载器,在Foo4.foo中, Foo类型的加载器为appClassLoader。JVM在运行时会对这两个加载器进行验证,JVM规范中要求这两个加载器必须要一致,否则将报类验证错误,即VerifyError的错误,这是为了防止不正常的类冒充正确的类进行类型欺骗。如在类Foo3中的Foo是来自于黑客故意构建的一个类时。
我们再来看关于jbmp的问题,这是由于引用juel.jar时,里面有一个类如ExpressionFactory类,此类在类JspApplicationContext中被声明。在juel.jar中,类ExpressionFactory已经被jspClassLoader加载了,现在要进行赋值语句,即=由jspContext中取得的expressionFactory对象。而JspApplicationContext是由Tomcat的StandardClassLoader类加载的,在类JspApplicationContext中声明的expressionFactory字段自然也是由StandardClassLoader类加载的。现在两个由不同类加载器加载的同一个对象要进行引用操作,自然不能通过JVM的验证了。
总而言之,就是说JVM在引用其它类的字段,或者调用其它类的方法时,将进行类型验证。验证包括,字段的类型验证,方法的返回类型验证,方法参数类型验证等。验证的内容就验证在调用方和被调用方时,同一个类的加载器是否一致。即在调用方时,记录的字段(参数)类型的加载器与被调用方法记录的字段(参数)类型的加载器是否一致。如果不一致,自然就不会被JVM验证通过。
ref:http://www.iflym.com/index.php/code/understand-jvm-load-constraint.html
上面这篇blog和我在项目中遇到的问题是一致的,我们在项目中需要对旧版本的Class对象就行替换,之前的做法仅仅是把Impl中值给替换了,之后在debug过程中发现这样是不够的,因为在tuscany的加载过程中它会对具体的implementation实现进行Introspection来检查这个实现中有哪些Reference、Service等等,它会将Reference的字段保存下来,然后在运行过程中通过相应的Injector来进行注入,一开始的做法,我们是将Injector里面保存的method和field用新版本的给替换(因为我们发现tuscany里面的注入的具体实现是通过反射来实现的),这样改完之后运行时就出现了上面blog中出现的问题。
仔细分析了下错误原因,我们发现tuscany通过反射注入的值是旧版本的,而我们的method、field对象都是新版本的,这样就会出现IllegalArgumenException错误,分析之后得出结论:tuscany用来生成注入值所使用的字段接口仍然是旧版本的,也就是说我们的替换不完全,通过对WireObjectFactory中保存的interfaze的替换,将旧版本从中移除,这样反射时就不会出错了
在调试过程中还遇到另外一个问题,由于field是private类型的,当我们需要对它进行注入时取消java语言访问控制检查
类A中有一个字段a,它的类型为X
类B中有一个字段b,它的类型也为X
类A由classLoaderA所加载,类B由classLoaderB所加载
执行赋值语句A.a = B.b,由于这两个类型均为X,可以执行,但是有一个要求,这个要求就是在A中所装载类X的装载器必须和在B中装载类X的装载器相同,否则赋值语句失败
为什么会产生上面的输出,我们可以来看一个以下的代码
首先是一个简单的类调用:
类Foo3
public class Foo3 implements IFoo{ public void hello() throws Exception{ Class<?> clazz = Foo.class; Foo foo2 = Foo4.foo; } }
在上面的代码中,变量foo2引用了类Foo4的一个静态引用:
public class Foo4 { public static Foo foo = new Foo(); }
类Foo是一个非常简单的java类,即普通的java类:
public class Foo implements IFoo{}
重点在于如何运行这段代码,我们运行一段代码,分别使用两个类加载器来加载同一个类类Foo,运行代码如下
MyClassLoader3 myClassLoader3 = new MyClassLoader3(T.class.getClassLoader()); IFoo foo3 = (IFoo) (myClassLoader3.loadClass("com.m_ylf.study.java.classLoad.Foo3").newInstance()); foo3.hello();
在上面的代码中,采用自定义的classLoader来定义类Foo3,我们来看具体的定义:
public Class<?> loadClass(String name) throws ClassNotFoundException { if("Foo".equals(name) ) { //自定义 } if("Foo3".equals(name) ) { //自定义 } return super.loadClass(name); }
其实就是将类类Foo和类Foo3交由classLoader3即我们自定义加载器来加载,其它的类仍交由super即appClassLoader来加载。现在运行这段代码,即会有一个出错信息,出错信息如下:
Exception in thread "main" java.lang.LinkageError: loader constraint violation: when resolving field "foo" the class loader (instance of MyClassLoader3) of the referring class, Foo4, and the class loader (instance of sun/misc /Launcher$AppClassLoader) for the field's resolved type, /Foo, have different Class objects for that type at Foo3.hello(Foo3.java:7)
错误在第7行,即Foo foo2 = Foo4.foo;这一行出错了。
为什么会出错,我来看来第一行代码:Class<?> clazz = Foo.class;这段代码,会对Foo类进行加载,采用的加载器为myClassLoader3,即加载Foo3类时所使用的加载器。这句话运行之后,即表示类Foo已经被加载了,且加载器为myClassLoader3。
第二行代码:Foo foo2 = Foo4.foo。这段代码会初始化Foo4,由于myClassLoader3并没有特殊处理Foo4,所以将由父类加载器,即AppClassLoader来加载,在加载过程中,因为调用到了Foo4.foo,所以会加载Foo类。这个加载是在Foo4类初始化时进行加载的。因为在碰到类Foo时,appClassLoader显示其从未加载过foo(先前的foo是由myClassLoader加载的,而不是由appClassLoader加载的),所以又会加载Foo。
这时候,类Foo就会有两个类加载器,一个是由myClassLoader3加载的,另一个是由appClassLoader加载的。如果两个类分开运行,代码是没有问题的。
问题就出在这个赋值语句,或者说是对象引用上。在Foo3内部使用Foo4.foo时,JVM会记录Foo4.foo在foo3内部的类引用和加载器,在这个运行代码中,此加载器为myClassLoader,因为在调用Foo4.foo之前已经加载了Foo。然而,在引用时,它将得到声明Foo4.foo时的Foo类型的加载器,在Foo4.foo中, Foo类型的加载器为appClassLoader。JVM在运行时会对这两个加载器进行验证,JVM规范中要求这两个加载器必须要一致,否则将报类验证错误,即VerifyError的错误,这是为了防止不正常的类冒充正确的类进行类型欺骗。如在类Foo3中的Foo是来自于黑客故意构建的一个类时。
我们再来看关于jbmp的问题,这是由于引用juel.jar时,里面有一个类如ExpressionFactory类,此类在类JspApplicationContext中被声明。在juel.jar中,类ExpressionFactory已经被jspClassLoader加载了,现在要进行赋值语句,即=由jspContext中取得的expressionFactory对象。而JspApplicationContext是由Tomcat的StandardClassLoader类加载的,在类JspApplicationContext中声明的expressionFactory字段自然也是由StandardClassLoader类加载的。现在两个由不同类加载器加载的同一个对象要进行引用操作,自然不能通过JVM的验证了。
总而言之,就是说JVM在引用其它类的字段,或者调用其它类的方法时,将进行类型验证。验证包括,字段的类型验证,方法的返回类型验证,方法参数类型验证等。验证的内容就验证在调用方和被调用方时,同一个类的加载器是否一致。即在调用方时,记录的字段(参数)类型的加载器与被调用方法记录的字段(参数)类型的加载器是否一致。如果不一致,自然就不会被JVM验证通过。
ref:http://www.iflym.com/index.php/code/understand-jvm-load-constraint.html
上面这篇blog和我在项目中遇到的问题是一致的,我们在项目中需要对旧版本的Class对象就行替换,之前的做法仅仅是把Impl中值给替换了,之后在debug过程中发现这样是不够的,因为在tuscany的加载过程中它会对具体的implementation实现进行Introspection来检查这个实现中有哪些Reference、Service等等,它会将Reference的字段保存下来,然后在运行过程中通过相应的Injector来进行注入,一开始的做法,我们是将Injector里面保存的method和field用新版本的给替换(因为我们发现tuscany里面的注入的具体实现是通过反射来实现的),这样改完之后运行时就出现了上面blog中出现的问题。
仔细分析了下错误原因,我们发现tuscany通过反射注入的值是旧版本的,而我们的method、field对象都是新版本的,这样就会出现IllegalArgumenException错误,分析之后得出结论:tuscany用来生成注入值所使用的字段接口仍然是旧版本的,也就是说我们的替换不完全,通过对WireObjectFactory中保存的interfaze的替换,将旧版本从中移除,这样反射时就不会出错了
在调试过程中还遇到另外一个问题,由于field是private类型的,当我们需要对它进行注入时取消java语言访问控制检查
newField.setAccessible(true);
相关文章推荐
- Java ClassLoader基础及加载不同依赖 Jar 中的公共类
- Java ClassLoader基础及加载不同依赖 Jar 中的公共类
- Java ClassLoader基础及加载不同依赖 Jar 中的公共类
- 学习Java的第一步是安装好JDK,写一个Hello World, 其实JDK的学习没有那么简单,关于JDK有两个问题是很容易一直困扰Java程序员的地方:一个是CLASSPATH的问题,其实从原理上来说,是要搞清楚JRE的ClassLoader是如何加
- Java ClassLoader基础及加载不同依赖 Jar 中的公共类
- 使用网上流传的一个数据库连接池在Proxy.newProxyInstance处引起 java.lang.ClassCastException 问题的解决方法
- Java ClassLoader加载.jar包中的Class
- Java中class是如何加载到JVM中的(Class.forName("name")和ClassLoader.loadClass("name")的区别)
- 分析Java的类加载器与ClassLoader(二):classpath与查找类字节码的顺序,分析ExtClassLoader与AppClassLoader的源码
- Java 类加载体系与ContextClassLoader
- log4j 多classloader重复加载配置问题解决
- 构建maven的web项目时注意的问题(出现Error configuring application listener of class org.springframework.web.context.ContextLoaderListener 或者前端控制器无法加载)
- Javaweb使用getContextClassLoader().getResource("")加载路径问题
- 使用java命令运行class文件提示“错误:找不到或无法加载主类“的问题分析
- tomcat启动问题---java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener
- Android中ClassLoader和java中ClassLoader有什么关系和不同
- java动态加载指定的类或者jar包反射调用其方法-涉及其他jar中的类就报ClassNotFound问题分析及解决思路
- Java 类加载机制 ClassLoader Class.forName 内存管理 垃圾回收GC
- 使用java命令运行class文件提示“错误:找不到或无法加载主类“的问题分析
- java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener问题