ClassLoader 深入解析 (二)

2014-11-24 01:25:06 · 作者: · 浏览: 2
public static int counter2 = 0;(C)

在加载完后的连接阶段的准备期,会为singleton分配内存,设定默认值为null,counter1默认值为 0,counter2 默认值为 0. 进入初始化阶段,第一步为singleton赋值 会调用Singleton的构造方法,此时执行counter1++,counter2++ counter1 = 1,counter2 = 1;第二步为counter1赋值,由于没有赋值语句counter1 仍为1;第三步为 counter2赋值,counter2 被赋值为 0,所以结果是counter1 =1

Counter2 =0

在case2 中 使用如下语句

public static int counter1;(A)

public static int counter2 = 0;(B)

private static Singleton singleton = new Singleton();(C)

连接准备阶段结束后counter1 = 0,counter2 = 0,singleton = null;初始化时 第一步A语句不用初始化 counter1 =0;第二部B语句初始化为0,counter2 =0;第三步 调用构造函数 counter1++,counter2++ counter1 = 1,counter2 = 1;所以case2的结果为 counter1 =1 counter2 = 1

在下面的部分,我将给大家深入的介绍一下JVM的类加载器。


类加载器分析
在上面的例子中我们看到一个类的主动使用会经历加载、链接、初始化的过程,类的加载需要使用JVM的类加载器,JVM的类加载器使用父亲委托机制。我们首先看看JVM的类加载器树形结构:

BootStrap(根类加载器)

||

Extend(扩展类加载器)

||

System(系统类或应用类加载器)

||

用户自定义类加载器


从上面的结构可以看出,JVM的类加载器一共有四大类,其中根类加载器、扩展类加载器、系统类加载器(应用类加载器)为JVM自带的类加载器。

1.根类加载器。负责加载虚拟机的核心类库,如java.lang.*等。根类加载器从系统 属性sun.boot.class.path 所指定的目录中加载类库。根类加载器的实现依赖于底层 操作系统,属于jvm实现的一部分,使用c++语言编写。它并没有ClassLoader类, 也没有父加载器。使用如stringObject.getClass().getClassLoader()将返null。

2. 扩展类加载器(Ext)。从图中可以看出它的父加载器是根加载器。它 从java.ext.dirs系统属性所指定的目录中加载类库,或者从jdk的安装目录 的jre\lib\ext子目录下加载类库,如果你把用户创建的jar放在这个目录下会被扩展类 加载器加载。扩展类加载器使用纯java实现,继承了ClassLoader。

3. 系统类加载器,也叫应用类加载器(APP),它的父加载器是Ext加载器。它从 环境 变量里(安装JDK时设立的classpath)或从系统属性java.class.path加载 类,它是 用户自定义的类加载器的默认父加载器,采用纯java实现,继承 自ClassLoader。

4. 用户自定义加载器。系统类加载器的子类,必须要继承自ClassLoader之类,并 且重 写findClass方法。

假设我们自定义ClassLoader 为loadA,并且使用loadA.load(Class),所谓的父亲委托机制就是loadA 委托父加载器(App)加载Class,App委托Ext,Ext委托BootStrap,如果BootStrap不能加载,则让Ext加载,逐级下发,如果直到loadA还不能加载Class这抛出ClassNotFindException。委托机制是SUN公司基于安全性考虑的,这样可以保证Object这样的重要类只能有JVM加载。我们定义若一个类加载器能够成功加载类Class,我们则称这个加载器为该类的定义加载器,其下的子加载器为初始化加载器。如在上述的类之中,假设App类加载器加载了类Class 这App为定义加载器,APP与LoadA为初始化加载器。

需要指出的是加载器之间的父子关系并不是指类之间的继承关系,而是指加载器之间的包装关系。一对父子可能是同一个类加载器的实例,也可能不是。例如我们自定义类加载器MyClassLoader。LoadA = new MyClassLoader(); LoadB = new MyClassLoader(loadA), 我们称loadB包装了loadA,LoadA是loadB的父加载器。

运行时包决定了protecetd类和protected成员是否能够访问。我们知道所有的protected的成员需要同一个包下的类才能访问。如果我们定义java.lang.Spy类,我们是否就能访问java.lang.*下的核心protected资源呢?运行时包包括,包名相同,类加载器相同,所有java.lang.Spy 与java.lang.* 不在相同的运行时包,答案是否定的。

下面我们以视频中一个详细的例子来讲述用户自定义加载器的实现。首先给出例子中类加载器的树形关:

BootStrap(根类加载器)

|| \\

|| LoaderC -----> d:app/otherlib

Extend(扩展类加载器)

||

System(系统类或应用类加载器)

||

LoaderB ------> d:app/serverlib

||

LoaderA ------> d:app/clientlib


在例子中我们定义三个类加载器,在重写的findClass方法中设定好加载路径。JDK API给出一个自定义ClassLoad的方法:

class MyClassLoader extends ClassLoader {

public Class findClass(String name) {

byte[] b = loadClassData(name);

return defineClass(name, b, 0, b.length);

}

private byte[] loadClassData(String name) {

// load the class data from the connection

. . .

}

}


从上面的代码可以看出通过重写findClass方法并在loadClassData