您的位置:首页 > 编程语言 > Java开发

11.《深入理解Java虚拟机》类加载器与双亲委派模型

2017-01-02 20:17 330 查看
2017-1-2 20:20 写了一天的blog和看书,看不下去了,回去健身。明天再写。

2017-1-2 23:00 回到寝室我还是妥协了,还是先把这个写完吧。

类与类加载器

虚拟机设计团队把类的加载阶段中的”通过一个类的全限定名来获取此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为”类加载器”。

类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限定于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话表达地再简单一点就是:比较两个类是否”相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则即使这两个类来源于同一个.class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,这两个类必定不相等。

上面说的”相等”,包括代表类的.class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括使用instanceof关键字做对象所属关系判定等情况。

下面给出了一个示例显示不同的类加载器对instanceof关键字运算结果的影响:

package JVM;

import java.io.IOException;
import java.io.InputStream;

/**
* Created by louyuting on 17/1/2.
*/
public class ClassLoaderTest {

public static void main(String[] args) throws Exception{
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".")+1)+".class";
InputStream is = getClass().getResourceAsStream(fileName);
if(is == null){
return super.loadClass(name);
}

byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}

}
};

Object obj = myLoader.loadClass("JVM.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.print(obj instanceof JVM.ClassLoaderTest);
}
}


上述代码的运行结果是:



代码里面构造了一个简单的类加载器,加载同一路径下面的class文件。我使用我自己定义的类加载器去加载JVM.ClassLoaderTest这个类,并实例化该对象。从输出结果可知,该类确实是JVM.ClassLoaderTest实例化出来的对象。但是从第二个false可知,该对象与类JVM.ClassLoaderTest做类型检查返回了false,这是因为虚拟机中存在了两个JVM.ClassLoaderTest类,一个是我自己定义的类加载器加载的,一个是系统应用程序类加载器加载的。虽然来自同一个class文件但是依然是两个独立的类,做对象所属类型检查时结果自然是false。

双亲委派模型

从虚拟机角度看只存在两种类型的类加载器:

1. 一种是启动类加载器(BootStrap ClassLoader),这个加载器使用C++实现,是虚拟机自身的一部分。

2. 另外一种就是所有其他类加载器,这些类加载器都是由java语言实现的,独立于虚拟机外部,并且全部继承与java.lang.ClassLoader。

从开发人员角度看可以划分更加细:



1、启动类加载器Bootstrap ClassLoader

之前说过了这是一个嵌在JVM内核中的加载器。它负责加载的是JAVA_HOME/lib下的类库,或则被-Xbootclasspath参数指定的路径中的,并且是虚拟机是别的类库加载到虚拟机内存中。系统类加载器无法被Java程序直接应用

2、扩展类加载器Extension ClassLoader

这个类加载器由sun.misc.Launcher$ExtClassLoader实现,它负责用于加载JAVA_HOME/lib/ext目录中的,或者被java.ext.dirs系统变量指定所指定的路径中所有类库,开发者可以直接使用扩展类加载器。java.ext.dirs系统变量所指定的路径的可以通过程序来查看

System.out.println(System.getProperty("java.ext.dirs"));


运行结果:

/Users/louyuting/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.7.0_72.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java


3、应用程序类加载器Application ClassLoader

这个类加载器由sun.misc.Launcher$AppClassLoader实现。这个类也一般被称为系统类加载器。

我们的应用程序都是由这3种类加载器互相配合进行加载的,有必要还可以加入自定义的类加载器。

下图展示了类加载器之间的层次关系:



这里的类加载器之间的父子关系不是继承关系,而只是层次上的定义。它并不是一个强制性的约束模型,而是Java设计者推荐给开发者的一种类加载器实现方式。

双亲委派模型的工作过程是:

1. 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。

2. 所有的加载请求最终都会传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

所以,其实所有的加载请求最终都应该传送到顶层的启动类加载器中。双亲委派模型对于Java程序的稳定运作很重要,因为Java类随着它的加载器一起具备了一种带有优先级的层次关系。例如java.lang.Object,存放于rt.jar中,无论哪一个类加载器要去加载这个类,最终都是由Bootstrap ClassLoader去加载,因此Object类在程序的各种类加载器环境中都是一个类。相反,如果没有双亲委派模型,由各个类自己去加载的话,如果用户自己编写了一个java.lang.Object,并放在CLASSPATH下,那系统中将会出现多个不同的Object类,Java体系中最基础的行为也将无法保证,应用程序也将会变得一片混乱。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息