在Java中类加载器是用来把类加载到Java虚拟机中的。从JDK 1.2 版本开始,类的加载过程使用双亲委托机制,这种机制能够更好的保证Java平台的安全。在此委托机制中,除了Java虚拟机自带的2类加载器外,其余加载器都有且只有一个父加载器。当Java程序员请求加载器loader1加载Sample类时,loader1会首先委托自己的父加载器去加载Sample类,若父加载器能加载,则由父加载器完成加载任务,否则才由加载器loader1本身加载Sample类。
Java虚拟机自带了以下几种类加载器:
java.lang.Object
就是由根类加载器加载的。根类加载器从系统属性sun.boot.class.path
所指定的目录中加载类库,例如根类加载器会加载$JAVA_HOME
中jre/lib/rt.jar
里所有的class。根类加载器的实现依赖于底层操作系统,由C++实现,属于虚拟机实现的一部分,它并没有继承java.lang.ClassLoader
类。java.ext.dirs
系统属性所指定目录中加载类库,或者从JDK的安装目录jre\lib\ext
子目录(扩展目录)下加载类库,如果把用户创建的JAR
文件放在这个目录下,也会由扩展类加载器加载。扩展类加载器是纯Java
类,是java.lang.ClassLoader
类的子类。java.class.path
所指定的目录中加载类,它是用户自定义类加载器的默认父加载器。系统类加载器是纯Java类,是java.lang.ClassLoader
类的子类。如下图所示:
假设我们要使用自定义的类加载器CustomClassLoader
去加载一个Sample
类,那么CustomClassLoader
会优先将加载这个类的任务委托给其父加载器即系统类加载器AppClassLoader
,AppClassLoader
首先查找Sample
类是否已经加载,如未加载又会优先将加载任务委托给其父加载器即扩展类加载器ExtensionClassLoader
,同理ExtensionClassLoader
也会首先查找Sample
类是否已经加载过,若未加载过会优先将加载任务委托给其父加载器即根类加载器BootstraoClassLoader
,根类架在器同样查找Sample
类是否已经加载过,若还是未加载过,因为根类加载器已经没有父加载器了,此时根类加载器会尝试去加载Sample
类,因为根类加载器只能从特定的目录下加载类,如果该目录下没有Sample
类则根类加载无法加载,接着由扩展类加载器ExtensionClassLoader
尝试去加载Sample
类,同理若还是无法加载则由系统类加载器尝试去加载Sample
类,以此类推从上往下查找能够加载Sample
类的类加载器,直到找到为止。
如果一个类能够成功的加载Sample
类,那么这个类加载器被称为定义类加载器,所有能成功返回Sample
类的Class对象引用的类加载器(包括定义类加载器)都被称为初始类加载器。如下图所示:
假设最开始由loader2
尝试去加载Sample
类,但最终是系统类加载器成功加载了Sample
类,那么系统类加载器就称为定义类加载器,而loader2
及其父加载器loader2
和系统类加载器则都是Sample
类的初始类加载器,因为这三者都能够返回Sample
类的Class
对象的引用。
ClassLoader
:clazz.getClassLoader()
ClassLoader
:Thread.currentThread.getContextClassLoader()
ClassLoader
:ClassLoader.getSystemClassLoader()
ClassLoader
:DriverManager.getCallerClassLoader()
ClassLoader
类是一个抽象类,给定一个类的二进制名称,类加载器应该能够定位或者生成构成该类定义的运行时数据(我的理解就是这个类的Class对象),每一个Class
对象都包含一个Class.getClassLoader()
来获取定义该类的类加载器,但对于数组类型的Class
对象不是由并不是由类加载器创建的,数组类型的Class
对象是由JVM在运行期动态为我们生成的,但数组类型的Class
对象调用getClassLoader()
所返回的类加载器与数组元素的的类加载器是一样的,但如果该数组中元素属于原生类型,那么该数组就没有类加载器(注意这个对于根类加载器返回null时不一样的)。通过实现ClassLoader
我们可以实现自定义类加载器,从扩展JVM加载类的方式。
ClassLoader
使用了一种委托模型来查找类和资源,每一个ClassLoader
类的实例(即类加载器)都会关联一个父类加载器。类加载器在加载一个类之前会首先将加载类的动作委托给其父加载器,父加载器无法加载时才由自己加载。JVM内建了一个称为boot strap class loader
的类加载器,该加载器没有父加载器但可以作为其他类加载器的父加载器。
一个类加载器如果支持并行的加载类,则称之为并行类加载器
,并行类类加载器要求在初始化的时候调用ClassLoader
类的registerAsParallelCapable()
方法来注册自身。如果该类加载的子类也需要能够并行的加载类,则子类依然需要调用registerAsParallelCapable()
来注册自身,就是说不能因为父类注册过了子类就不需要注册了。
ClassLoader
类的defineClass
方法可以将一个字节数组转化为一个Class
对象,接着就可以使用该Class对象的newInstance
来创建对象了。
通过ClassLoader
类的loadClass
方法我们可以加载一个类,loadClass
方法的实现如下所示:
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 检查该类是否已经加载过
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
/**
* 检查父加载器是否为空,若不为空则尝试使用父加载器来加载该类,若为空则使用根加载器来加载该类
**/
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
/**
* 若父加载器和跟加载器都未能加载该类,则调用当前类加载器的findClass方法来加载该类,而findClass在ClassLoader类中实现就是抛出一个异常,需要在我们自定义的类加载器中实现,一般`findClass`的实现方式是首先加载类的二进制数据得到一个byte数组,然后调用ClassLoader的defineClass方法将该byte数组转换为Class对象即完成加载
**/
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
从代码实现中我们也可以看出,类加载器优先使用父加载器来加载类,当父加载器都无法加载时才使用当前类加载器来加载,当前类加载器调用ClassLoader
类的findClass
完成类的加载,而findClass
方法在ClassLoader
类中定义如下:
protected Class> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
只抛出了一个异常,需要在我们自定义的类加载器中实现,在自定义类加载器需要继承ClassLoader
类,findClass
实现中首先加载类的二进制数据得到byte
数组,然后调用defineClass
方法将二进制数组转换为Class
对象。defineClass
的实现如下:
protected final Class> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}
通过调用native方法defineClass1
将byte
数组转换为Class
对象。
每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成,在同一个命名空间中,不会出现类完整名字(包括类的包名)相同的两个类,在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的的两个类。
自定义类加载器需要继承ClassLoader
类,然后在构造方法中设置自定义ClassLoader
类的父加载器,最后实现实现findClass
方法即可,一个自定义的类加载器如下所示:
public class MyClassLoader extends ClassLoader {
// 保存当前类加载器的名称
private String classLoaderName;
private final String classExt = ".class";
private String path;
public MyClassLoader(String classLoaderName) {
// 调用super方法设置当前类加载器的父加载器为系统类加载器
super();
this.classLoaderName = classLoaderName;
}
public MyClassLoader(ClassLoader parent, String classLoaderName) {
// 调用super方法设置当前类加载器的父加载器为parent
super(parent);
this.classLoaderName = classLoaderName;
}
/**
* 设置类加载的路径,从当前项目外加载类,否则加载当前项目中的类永远是父加载器
* @param path
*/
public void setPath(String path) {
this.path = path;
}
@Override
public String toString() {
return "[" + classLoaderName + "]";
}
/**
* 实现findClass方法,根据类名name加载类的二进制数据得到byte数组,并调用defineClass方法将byte数组转换为Class对象
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
return defineClass(name, classData, 0, classData.length);
}
/**
* 根据类名name加载类的二进制数据
* @param name
* @return
*/
protected byte[] loadClassData(String name) {
System.out.println("MyClassLoader load class:" + name);
byte[] data = null;
// 将类的二进制名称转换具体类路径
name = this.path + '/' + name.replace('.', File.separatorChar) + classExt;
try(ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream fileStream = new FileInputStream(new File(name))) {
int ch = 0;
while (-1 != (ch = fileStream.read())) {
baos.write(ch);
}
data = baos.toByteArray();
}catch (Exception ex) {
ex.printStackTrace();
}
return data;
}
public static void main(String[] args) throws Exception {
/**
* myClassLoader1首先在自己的命名空间中查找Singleton是否已经加载过,没有加载过则委托其父加载器即系统类加载器去加载Singleton
* 类,系统类加载器同样在自己的命名空间查找Singleton类,同样没找到,委托其父加载器即启动类加载器去加载Singleton类,以此类推
* 最后发现父加载器都无法加载Singleton类,所以myClassLoader1调用findClass方法去加载Singleton类
*/
MyClassLoader myClassLoader1 = new MyClassLoader("MyClassLoader1");
myClassLoader1.setPath("/Users/peter/Desktop");
Class> clazz1 = myClassLoader1.loadClass("com.ctrip.flight.test.jvm.classloader.Singleton");
System.out.println("clazz1 : " + clazz1.hashCode());
System.out.println("clazz1 loader : " + clazz1.getClassLoader());
System.out.println("=====================================================================================");
/**
* myClassLoader1是myClassLoader2的父加载器,当myClassLoader2去加载Singleton类时,会首先在自己的命名空间中查找Singleton
* 是否已经加载过,由于myClassLoader2的命名空间已经包含了myClassLoader1的命名空间,而myClassLoader1已经加载了Singleton类,
* 因此myClassLoader2直接在自己的命名空间中找到Singleton类的Class对象并直接返回
*/
MyClassLoader myClassLoader2 = new MyClassLoader(myClassLoader1, "MyClassLoader2");
myClassLoader2.setPath("/Users/peter/Desktop");
Class> clazz2 = myClassLoader2.loadClass("com.ctrip.flight.test.jvm.classloader.Singleton");
System.out.println("clazz2 : " + clazz2.hashCode());
System.out.println("clazz2 loader : " + clazz2.getClassLoader());
System.out.println("======================================================================================");
/**
* myClassLoader3加载Singleton的过程和myClassLoader1加载Singleton的过程一样,只不过myClassLoader3的命名空间和myClassLoader1
* 的命名空间不一样
*/
MyClassLoader myClassLoader3 = new MyClassLoader("MyClassLoader3");
myClassLoader3.setPath("/Users/peter/Desktop");
Class> clazz3 = myClassLoader3.loadClass("com.ctrip.flight.test.jvm.classloader.Singleton");
System.out.println("clazz3 : " + clazz3.hashCode());
System.out.println("clazz3 loader : " + clazz3.getClassLoader());
}
}
运行结果如下:
MyClassLoader load class:com.ctrip.flight.test.jvm.classloader.Singleton
clazz1 : 1872034366
clazz1 loader : [MyClassLoader1]
=====================================================================================
clazz2 : 1872034366
clazz2 loader : [MyClassLoader1]
======================================================================================
MyClassLoader load class:com.ctrip.flight.test.jvm.classloader.Singleton
clazz3 : 1670675563
clazz3 loader : [MyClassLoader3]
当一个类被加载、链接和初始化后,它的生命周期就开始了。当代表该类的Class对象不再被引用,即不可触及时,该Class对象就会结束生命周期,该类在方法区内的数据也会被卸载,从而结束该类的生命周期。一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期。
由java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载,java虚拟机自带的类加载器包括根加载器、扩展类加载器和系统类加载器。java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用他们所加载的Class对象,因此这些Class对象始终是可触及的。
由用户自定义的类加载器所加载的类是可以被卸载的。如下示例演示类被卸载:
public class MyClassLoader extends ClassLoader {
// 保存当前类加载器的名称
private String classLoaderName;
private final String classExt = ".class";
private String path;
public MyClassLoader(String classLoaderName) {
// 调用super方法设置当前类加载器的父加载器为系统类加载器
super();
this.classLoaderName = classLoaderName;
}
public MyClassLoader(ClassLoader parent, String classLoaderName) {
// 调用super方法设置当前类加载器的父加载器为parent
super(parent);
this.classLoaderName = classLoaderName;
}
/**
* 设置类加载的路径,从当前项目外加载类,否则加载当前项目中的类永远是父加载器
* @param path
*/
public void setPath(String path) {
this.path = path;
}
@Override
public String toString() {
return "[" + classLoaderName + "]";
}
/**
* 实现findClass方法,根据类名name加载类的二进制数据得到byte数组,并调用defineClass方法将byte数组转换为Class对象
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
return defineClass(name, classData, 0, classData.length);
}
/**
* 根据类名name加载类的二进制数据
* @param name
* @return
*/
protected byte[] loadClassData(String name) {
System.out.println("MyClassLoader load class:" + name);
byte[] data = null;
// 将类的二进制名称转换具体类路径
name = this.path + '/' + name.replace('.', File.separatorChar) + classExt;
try(ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream fileStream = new FileInputStream(new File(name))) {
int ch = 0;
while (-1 != (ch = fileStream.read())) {
baos.write(ch);
}
data = baos.toByteArray();
}catch (Exception ex) {
ex.printStackTrace();
}
return data;
}
public static void main(String[] args) throws Exception {
/**
* 如下示例演示类被卸载
*/
MyClassLoader myClassLoader4 = new MyClassLoader("MyClassLoader4");
myClassLoader4.setPath("/Users/peter/Desktop");
Class> clazz4 = myClassLoader4.loadClass("com.ctrip.flight.test.jvm.classloader.Singleton");
System.out.println("clazz4 : " + clazz4.hashCode());
System.out.println("clazz4 loader : " + clazz4.getClassLoader());
myClassLoader4 = null;
clazz4 = null;
System.gc();
myClassLoader4 = new MyClassLoader("MyClassLoader5");
myClassLoader4.setPath("/Users/peter/Desktop");
clazz4 = myClassLoader4.loadClass("com.ctrip.flight.test.jvm.classloader.Singleton");
System.out.println("clazz4 : " + clazz4.hashCode());
System.out.println("clazz4 loader : " + clazz4.getClassLoader());
}
}
运行的时候加上-XX:+TraceClassUnloading
参数,输出结果如下所示:
MyClassLoader load class:com.ctrip.flight.test.jvm.classloader.Singleton
clazz4 : 1872034366
clazz4 loader : [MyClassLoader4]
[Unloading class com.ctrip.flight.test.jvm.classloader.Singleton 0x00000007c0061028]
MyClassLoader load class:com.ctrip.flight.test.jvm.classloader.Singleton
clazz4 : 1670675563
clazz4 loader : [MyClassLoader5]