一、类加载器
类加载器用来把类加载到Java虚拟机中。从JDK1.2版本开始,类的加载过程采用父亲委托机制,这种机制能更好地保证Java平台的安全。在此委托机制中,除了Java虚拟机自带的根类加载器以外,其余的类加载器都有且只有一个父加载器。当Java程序请求加载器loader1加载Sample类时,loader1首先委托自己的父加载器去加载Sample类,若父加载器能加载,则由父加载器完成加载任务,否则才由加载器loader1本身加载Sample类。
Java虚拟机自带了以下几种加载器
(1)根(Bootstrap)类加载器:也是启动类加载器。该加载器没有父加载器。它负责加载虚拟机的核心类库,如java.lang.*等。java.lang.Object就是由根类加载器加载的,根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。根类加载器的实现依赖于底层操作系统,属于虚拟机的实现的一部分,它并没有继承java.lang.ClassLoader类。
(2)扩展(Extension)类加载器:它的父加载器为根类加载器,它从java.ext.dirs系统属性所指定的目录中加载类库,或者从jdk的安装目录的jre\lib\ext子目录(扩展目录)下加载类库。如果把用户创建的jar文件放在这个目录下,也会自动由扩展类加载器加载。扩展类加载器是纯Java类,是java.lang.ClassLoader类的子类。
(3)系统(System)类加载器:也称为应用类加载器,它的父加载器为扩展类加载器。它从环境变量classpath或者系统属性java.lang.path所指定的目录中加载类,它是用户自定义的类加载器的默认父加载器。系统类加载器是纯Java类,是java.lang.ClassLoader类的子类。
(4)除了以上虚拟机自带的加载器外,用户还可以定制自己的类加载器。Java提供了抽象类java.lang.ClassLoader,所有用户自定义的类加载器都应该继承ClassLoader类。
各个类加载器委托加载机制和默认加载路径如下图:
如上如,在父亲委托机制中,各个加载器按照父子关系形成了逻辑意义上的树形结构,除了根类加载器之外,其余的类加载器都有且只有一个父加载器。
Class sampleClass = loader2.loadClass(“Sample”);
loader2 首先从自己的命名空间中查找 Sample 类是否已经被加载,如果已经被加载,就直接返回代表 Sample 类的 Class 对象的引用。
如果 Sample 类还没有被加载,loader2首先请求loader1代为加载,loader1再请求系统类加载器代为加载,系统类加载器再请求扩展类加载器代为加载,扩展类加载器再请求根类加载器代为加载。若根类加载器和扩展类加载器都不能加载,则系统类加载器尝试加载,若能加载成功,则将 Sample 类所对应的 Class 对象的引用返回给 loader1,loader1再将引用返回给 loader2,从而成功将 Sample 类加载进虚拟机。若系统类加载器不能加载 Sample类,则loader1尝试加载 Sample 类,若loader1也不能成功加载,则loader2尝试加载。若所有的父加载器及loader2本身都不能加载,则抛出ClassNotFoundException异常。
若有一个类加载器能成功加载Sample 类,那么这个类加载器被称为定义类加载器,所有能成功返回Class对象的引用的类加载器(包括定义类加载器)都被称为初始类加载器。
假设loader1实际加载了Sample类,则loader1为Sample类的定义类加载器,loader2和loader1为 Sample 类的初始类加载器。
需要指出的是,加载器之间的父子关系实际上指的是加载器对象之间的包装关系,而不是类之间的继承关系。一对父子加载器可能是同一个加载器类的两个实例,也可能不是。在子加载器对象中包装了一个父加载器对象。例如以下loader1和loader2都是MyClassLoader类的实例,并且loader2包装了loader1,loader1是loader2的父加载器。
ClassLoader loader1 = new MyClassLoader();
//参数loader1将作为loader2的父加载器
ClassLoader loader2 = new MyClassLoader(loader1);
类加载器的双亲委托模型的好处:
(1)可以确保Java核心库的类型安全:所有的Java应用都至少会引用java.lang.Object类,也就是说在运行期java.lang.Object这个类会被加载到Java虚拟机中;如果这个加载过程是由Java应用自己的类加载器所完成的,那么很可能就会在JVM中存在多个版本的java.lang.Object类,而且这些类之间还是不兼容的,相互不可见的(正是命名空间在发挥作用)。借助于双亲委托机制,Java核心类库中的类的加载工作都是由启动类加载器来统一完成,从而确保了Java应用所使用的都是同一个版本的java核心类库,他们之间是相互兼容的。
(2)可以确保Java核心类库所提供的类不会被自定义的类所替代。
(3)不同的类加载器可以为相同名称(binary name)的类创建额外的命名空间。相同名称的类可以并存在Java虚拟机中,只需要用不同的类加载器来加载他们即可。不同类加载器所加载的类之间是不兼容的,这就相当于在Java虚拟机内部创建了一个又一个相互隔离的Java类空间,这类技术在很多框架中都得到了实际的应用。
命名空间
每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。
同一个命名空间内的类是相互可见的。
子加载器的命名空间包含所有父加载器的命名空间。因此由子加载器加载的类能看见父加载器加载的类。例如系统类加载器加载的类能看见根类加载器加载的类。
由父加载器加载的类不能看见子加载器加载的类。
如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见。
由同一类加载器加载的属于相同包的类组成了运行时包。决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看定义类加载器是否相同。只有属于同一运行时包的类才能互相访问包可见(即默认访问级别)的类和类成员。这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员。假设用户自己定义了一个类java.lang.Spy,并由用户自定义的类加载器加载,由于java.lang.Spy和核心类库java.lang.*由不同的加载器加载,它们属于不同的运行时包,所以java.lang.Spy不能访问核心类库java.lang包中的包可见成员。
二、类的卸载
当Sample类被加载、连接和初始化后,它的生命周期就开始了。当代表Sample类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期,Sample类在方法区内的数据也会被卸载,从而结束Sample类的生命周期。由此可见,一个类何时结束生命周期,取决于代表他们的Class对象何时结束生命周期。
由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。前面已经介绍过,Java虚拟机自带的类加载器包括根类加载器、扩展类加载器和系统类加载器,Java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象,因此这些Class对象始终是可触及的。
用户自定义的类加载器所加载的类是可以被卸载的。