`
kangzhanlei
  • 浏览: 4115 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

loader constraints测试

    博客分类:
  • java
 
阅读更多

深入java虚拟机里说,无论何时java虚拟机遇到某些指向被引用类新的字段或者方法的符号引用,且被引用的类型的初始装载并非是初始装载引用类型的同一个类装载器,虚拟机就会在列表上加一个约束,虚拟机在解析符号引用的时候必须检查当前已经能够装载的所有约束。

从上面的说明来看,加约束的前提是存在多个类加载器,并且发生了引用关系,下面写一个测试案例。

首先定义一个普通的类:

public class Constraint {

}

 定义一个普通的B类

public class B {
	public void test(Constraint l) {
		Constraint ll = new Constraint();
	}
}

 一个普通的A类,其中首先产生一个Constraint类,然后加载B类,通过B去传递一个实例到B的test方法

public class A {
	public A() {
		Constraint constraint = new Constraint();
		B b = new B();
		b.test(constraint);
	}
}

 然后是2个classLoader,一个classLoader不会发生类加载约束。

classLoader2:

public class ClassLoader2 extends URLClassLoader {
	
	/**
	 * @param urls
	 * @param parent
	 */
	public ClassLoader2(URL[] urls) {
		super(urls);
	}
}

 另一个classLoader,这个类有些特殊,如果发现加载的类是B的话,直接委托给classLoader1去加载

public class ClassLoader1 extends URLClassLoader {
	private URL[] urls = null;

	/**
	 * @param urls
	 */
	public ClassLoader1(java.net.URL[] urls) {
		super(urls);
		this.urls = urls;
	}

	@Override
	public Class<?> loadClass(String name) throws ClassNotFoundException {
		if (name.equals("B")) {
			return new ClassLoader2(urls).loadClass("B");
		} else {
			return super.loadClass(name);
		}
	}

}

 一个启动类:

public class Start {
	public static void test()
	{
		A a = new A();
	}
}

 最后我们装配起来:

public class Main {
	public static void main(String[] args) throws Exception {
		URL binPath = Main.class.getClassLoader().getResource(".");
		File parent = new File(binPath.toURI()).getParentFile();
		URL cp1 = new File(parent, "init").toURI().toURL();
		ClassLoader1 cl1 = new ClassLoader1(new URL[] { cp1 });
		Class<?> clazz = cl1.loadClass("Start");
		Method method = clazz.getMethod("test", null);
		method.invoke(clazz.newInstance(), null);
	}
}

 

以上就是所有的代码了。我们来分析一下流程。

首先将编译好的A,B,Constraint,start类方法init目录下,防止他们被系统类加载器加载到。

启动是从Main类的main方法开始的,启动classLoader1去加载start类,调用Start的test方法。Strat类里用到的所有的类都会由ClassLoader1去加载,也就是说Start里的A是有classLoader1加载的。

A初始化的时候需要用到Constraint类,此时发现classLoader1是可以加载Constraint的,所有他将加入到classLoader1的命名空间里去。下一步需要加载B,此时发现classLoaer1直接将B委托给了classLoader2去加载,这个时候就出现了多个类加载器了,前提条件1已经满足了。classLoader2成功加载了B。此时我们应该想到,仅仅出现了多个类加载器,并没有发生引用关系,所有虚拟机不会去增加加载约束,事实上也是这样的。我们可以给Main方法启动的时候增加一个虚拟机参数:-XX:+TraceLoaderConstraints ,这个参数适用于jdk1.6之后。如有有发生加载约束的情况,他会打印到标准输出。

下一步就是要调用B.test(constraint)方法了,此时引用类A,用classLoader1加载,被引用的类B用classLoader2加载,两者发生了引用关系,此时虚拟机就应该加上一个约束关系,这个约束关系就是

[Adding new constraint for name: Constraint, loader[0]: ClassLoader1, loader[1]: ClassLoader2 ]

classLoader1加载的Constraint类必须和ClassLoader2加载的Constraint指向同一个数据类型。

下面接着走到了B的test方法里面,B会加载Constraint,因为B的加载器是classLoader2,所以依然会用ClassLoader2去加载Constraint,Constraint 的初始类加载器是classLoader2,此时就会违反了类加载约束。因为classLoader1里已经加载了类Constraint,假设classLoader2里也能加载成功,那么classLoader2加载的类对classLoader1是可见的(因为委托关系,定义类加载器加载的类对初始类加载器可见),那么classLoader1里就会有2个Constraint,对于classLoader1来说就不知道应该引用哪个了,所以增加加载约束是必须的。运行到这个代码段的时候就报错了:

 

Caused by: java.lang.LinkageError: loader constraint violation: loader (instance of ClassLoader2) previously initiated loading for a different type with name "Constraint"

 如果是这样的话,那么如果B的test里不去调用new Constraint是不是就不会违反约束了呢?

那我们注释掉B的调用试试

public class B {
	public void test(Constraint l) {
//		Constraint ll = new Constraint();
	}
}

 运行的结果我们可以发现,加载约束已经装载了,但是没有发生loader constraint violation,原因就是没有用到Constraint类,仅仅是增加了约束,一旦有用到的时候,就会报错

[Adding new constraint for name: Constraint, loader[0]: ClassLoader1, loader[1]: ClassLoader2 ]
 

下面再看一下深入java虚拟机的介绍。

当解析一个包含在<C,L1>中的符号引用(它指向的是类<D,L2>中声明的类型T的字段)时,虚拟机必须产生下列装载约束,TL1=TL2

下面我们修改一下B类为:

public class B {
	Constraint cc = new Constraint();

	public void test(Constraint l) {
		// Constraint ll = new Constraint();
	}
}

 或者修改B为:

public class B extends Constraint{

	public void test(Constraint l) {
		// Constraint ll = new Constraint();
	}
}
 

在B类的成员变量里增加一个Constraint cc= new Constraint()的调用。

运行条件不变,运行的过程中classLoader1会首先加载Constraint类(A类的调用),而classLoader2加载B的时候也会加载一个Constraint类,同一个类被不同的类加载器加载到了内存中,这是允许的,没有任何问题。但是执行到A类的b,test(constraint)的时候,他们建立了引用关系,此时要增加一个加载约束,下面是运行输出

[Failed to add constraint for name: Constraint, loader[0]: ClassLoader1, loader[1]: ClassLoader2, Reason: the class objects presented by loader[0] and loader[1] are different ]

 他发现要加载的Constraint是不一样的类型,于是增加约束的时候就失败了,直接抛了异常:

Caused by: java.lang.LinkageError: loader constraint violation: when resolving method "B.test(LConstraint;)V" the class loader (instance of ClassLoader1) of the current class, A, and the class loader (instance of ClassLoader2) for resolved class, B, have different Class objects for the type Constraint used in the signature
	at A.<init>(A.java:9)
	at Start.test(Start.java:12)
	... 5 more

 B期待的是classLoader2的Constrant,而A传给B的确是classLoaer1的Constraint,这两个不是一个类型。

 

 

 

以上都属于测试结果和个人的分析,不代表实际的运行原理,欢迎批评指正错误。

附上测试代码。

 

 

 

 

 

 

 

 

 

分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

Global site tag (gtag.js) - Google Analytics