Java内存管理-掌握虚拟机类加载器(五)
勿在流沙筑高台,出来混迟早要还的。
做一个积极的人
编码、改bug、提升自己
我有一个乐园,面向编程,春暖花开!
上一篇介绍虚拟机类加载机制,讲解了类加载机制中的三个阶段,分别是:加载、连接(验证、准备、解析)、初始化 ,知道了类加载的机制。下面我们就要知道类到底是通过什么方式加载到内存中的,也就是本文要介绍的类加载器,类加载器就是加载类的信息到内存中。
本文地图 :
一、什么是类加载器(ClassLoader)
虚拟机设计团队把类加载阶段中的”通过一个类的全限定名来获取描述此类的二进制字节流“这个动作是放到Java虚拟机外部去实现的,以便让应用程序自己决定如何去获取所需的类,实现这个动作的代码模块称为”类加载器“。
说简单一点:类加载器可以把类加载到Java虚拟机中,我们可以使用Java虚拟机自带的类加载器,也可以自定义实现自己的类加载器(自定义类加载器会在后面文章进行讲解)。
类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用远不限于类加载阶段。对于任意的一个类,都需要由加载它的类加载器和这个类本身一同确立起在Java虚拟机中的唯一性。每个类加载器都拥有一个独立的类命名空间(命名空间下面单独会介绍)。
说简单一点:比较两个类是否”相等“,只有在这两个类是由同一个类加载器加载的前提在才有意义,否则,即使这两个类源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类必定不相等。这里所指的“相等”包括代表类的Class对象的equal方法、isAssignableFrom()、isInstance()方法及instance关键字返回的结果。
如上图,此时虽然是同一个Person.class ,但是被不同的类加载器加载到Java虚拟机,那么加载后的这两个 Person类是不”相等“,因为它们分别在两个不同的命名空间中。
注:如果上面两个类的是否”相等“比较你没看懂的话,后面自定义类加载器的时候我们会演示一下这个例子,进行简单说明。
二、类加载器分类
一张图看懂类加载器分类:
上图的类加载器主要分为四类:
- Bootstrap ClassLoader : 启动类加载器
- Extension ClassLoader : 扩展类加载器
- Application ClassLoader :应用程序类加载器
- User ClassLoader :自定义类加载器
从虚拟机角度分析,类加载器分为两类: 一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另一种是其他类加载器,这些类加载器是Java语言实现的,独立于虚拟机之外,都继承自抽象类java.lang.ClassLoader。
public class ClassLoaderDemo {
public static void main(String[] args) {
// ClassLoaderDemo 的类加载器
System.out.println(ClassLoaderDemo.class.getClassLoader());
// 打印每个 ClassLoader的父加载器
ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
while (classLoader != null){
System.out.println(classLoader);
classLoader = classLoader.getParent();
}
System.out.println(classLoader);
}
}
--- 打印结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1540e19d
null
启动类加载器(根加载器)
主要负责加载存放在JAVA_HOME/jre/lib/rt.jar里面所有的class文件,或者被-Xbootclasspath参数所指定路径中以rt.jar命名的文件。启动类加载器无法被Java程序直接引用,如果在编写自定义类加载时,需要把加载的请求委派给启动类加载器,那么直接使用null代替即可。
例如:
MyClassLoader myClassLoader= new MyClassLoader(null); //父加载器使用启动类加载器
public MyClassLoader(ClassLoader parent){
super(parent);//指定该类加载器的父类加载器
}
扩展类加载器
这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载AVA_HOME/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。
应用程序类加载器
这个加载器由sun.misc.Launcher$AppClassLoader实现,它负责加载classpath对应的jar及目录。如果应用程序没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
自定义类加载器
用户定制自己的类加载器,继承ClassLoader,根据自己的需要进行设计和实现,比如需要从指定的路径下读取class文件进行加载等。
知道上面这些内容,你就应该联想到为什么在学习Java的时候首先要配置Java环境变量了。这就是知其所以然的过程。
思考:这么多类加载器,那么如何保证一个类不被重复加载,只被加载一次呢?
从JDK1.2版本开始,类加载过程中采用了双亲委派模型(父亲委派机制),这种机制能更好的保证Java平台的安全,在此委托机制中,除了Java虚拟机自带的根类加载(根加载器最顶级,无父加载器)以外,其余的类加载器都有且只有一个父加载器。双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有父类加载器反馈自己无法完成这个加载请求(它搜索的范围中没有找打所需的类)时,子加载器才会尝试自己去加载。
如下示意图:
在说明一下: 加载一个类的时候。首先自定义类加载类器先会委派给应用程序类加载器(Application ClassLoader),应用程序类加载器会委派给扩展类加载器(Extension ClassLoader),扩展类加载器会委派给启动类加载器(Bootstrap ClassLoader)去加载,这时候如果启动类加载器加载成功,则加载结束。如果加载失败,则交给扩展类加载器去加载,如果扩展类加载器加载成功,则加载结束。如果加载失败,则交给应用程序类加载器,如果应用程序类加载器加载成功,则加载结束。如果加载失败,则交给自定义类加载器。如果自定义类加载器加载成功,则加载结束。否则加载失败,会报ClassNotFoundExecption异常,结束。
每个类加载器都有自己的管辖范围(命名空间),并在自己的管辖范围做好自己的事情。
双亲委派模型还有一个优点是能提供软件系统的安全性,在这个机制下,用户自定义的类加载器不可能加载应该由父类加载器加载的可靠类,从而防止不可靠甚至是恶意的代码代替父类加载器加载可靠代码。如 java.land.Object 类总是由启动类加载器进行加载,其他任何用户自定义的类加载器都不可能加载含有恶意代码的java.land.Object 类。(每一个看似简单的设计背后,都是大师们智慧的结晶)
tips: 类加载器命名空间
每个类加载器都有自己的命名空间,命名空间由该加载器及所有父类加载器所加载的类组成。在同一个命名空间中,不会出现两个全类名(包名+类名)完全一样的类;在不同的命名空间中,有可能出现全类名相同的两个类。
一定要注意是:有可能出现全类名一样的,就如 Loader1 中自定义类加载器可以加载如 com.aflyun.HelloJVM.java , Loader2 中自定义类加载器也可以加载如 com.aflyun.HelloJVM.java 的类,两个互不影响,并且比较两个类的话,是不”相等“的。并且不同命名空间中类加载器加载的类,不能访问其他类加载器包中的包可见(即默认访问级别)成员。
三、总结
本文主要介绍了什么是类加载器以及类加载器的分类 ,让大家对类加载器相关的知识有一个整体的认识,这样我们也知道了哪些类加载器加载什么地方的数据。为我们后面对类加载器源码和分析以及实现自定义类加载器做好铺垫。预告:下一篇文章讲一下类加载器的源码分析和设计模式,以及实现一个自定义类加载器!
四、参考资料
《深入理解Java虚拟机》
推荐阅读
Java的线程安全、单例模式、JVM内存结构等知识梳理
Java内存管理-程序运行过程(一)
Java内存管理-初始JVM和JVM启动流程(二)
Java内存管理-JVM内存模型以及JDK7和JDK内存模型对比总结(三)
谢谢你的阅读,如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到!祝你每天开心愉快!
不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!
博客首页 : http://blog.csdn.net/u010648555
愿你我在人生的路上能都变成最好的自己,能够成为一个独挡一面的人
© 每天都在变得更好的阿飞云