基本概念
java的类加载器(class loader),用来将java的字节码文件(.class)读取到内存中,生成一个Java.lang.Class对象。通过Class类的newInstance()方法,可以生成一个被加载类的对象。
不同的类加载器可以从不同的来源来加载二进制字节码文件,主要包括:
- 本地文件
- 通过网路加载class文件
- 动态在内存中生成,如JSP文件
- 从JAR包中加载class文件
每一个Class类中包含加载该Class的class loader引用,通过Class.getClassLoader()方法可以得知。对于数组类型,其Class对象不是由类加载器创建的,而是由Java运行时动态创建的,如果数组的元素类型是引用类型,通过getClassLoader()方法将返回该引用类型的类加载器,如果是原生数据类型,则返回空。
Java中类加载器分为系统提供的类加载器和用户自定义的类加载器:
- 系统提供类加载器,包括根类加载器(Bootstrap)、扩展类加载器(Ext)和应用类加载器 (SystemClassLoader)
- 用户自定义的类加载器,通过继承抽象类java.lang.ClassLoader类实现,必须覆写findClass()方法,父类中该类的默认实现是抛出一个异常。
java的类加载器负责从不同的路径来源来加载二进制字节码文件,类加载器之前存在父子关系,加载类时通过双亲委托模型,将加载类的优先权层次上抛,只有当父类加载器无法加载时,才转交当前类加载器加载。
重要方法
- 默认构造方法
1protected ClassLoader() {
2 this(checkCreateClassLoader(), getSystemClassLoader());
3 }
可见,默认的父类加载器是应用类加载器
- 可以设置父类加载器的构造方法
1 protected ClassLoader(ClassLoader parent) {
2 this(checkCreateClassLoader(), parent);
3 }
设置父类加载器用于委托加载类。
- loadClass(String name) : 加载名称为name的类,包含加载类的总体流程
- findClass(String name):查找二进制clas文件并生成Class对象,被loadClass方法调用
- Class> defineClass(String name, byte[] b, int off, int len) :将二进制数据转换成Class对象,被findClass方法调用
双亲委托模型
如上图所示,一部了然了。系统提供的类加载器主要有下面三个:
- 引导类加载器(bootstrap class loader):也叫做启动类加载器用于加载JRE/lib/rt.jar中核心类。原生实现的,不继承自java.lang.ClassLoader类
- 扩展类加载器(extensions class loader):用于加载JRE/lib/ext目录下的类
- 系统类加载器:也叫做应用类加载器,用于当前项目CLASSPATH下的类
开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。各个加载器根据父子关系形成层级结构,除了根类加载器之外,其余的类加载器都有且只有一个父类加载器。在加载类时,一层一层向上委托,直到没有父类加载器为止,也就是到根加载器,然后从根类加载器开始向下逐层尝试加载,如果在某一层加载成功就宣告加载结束。这样保证了高层次的类加载器有加载类的优先权。
loadClass关键源码:
1protected Class<?> loadClass(String name, boolean resolve)
2 throws ClassNotFoundException
3 {
4 synchronized (getClassLoadingLock(name)) {
5 // First, check if the class has already been loaded
6 // 检查是否已经加载过,如果已经加载过,直接返回
7 Class<?> c = findLoadedClass(name);
8 if (c == null) {
9 long t0 = System.nanoTime();
10 try {
11 if (parent != null) {
12 // 委托给父类加载器加载
13 c = parent.loadClass(name, false);
14 } else {
15 // 如果父类加载器为空,说明当前是根类加载器
16 c = findBootstrapClassOrNull(name);
17 }
18 } catch (ClassNotFoundException e) {
19 // ClassNotFoundException thrown if class not found
20 // from the non-null parent class loader
21 }
22 // 父类加载器加载不成功,则自己亲自去加载
23 if (c == null) {
24 // If still not found, then invoke findClass in order
25 // to find the class.
26 long t1 = System.nanoTime();
27 // 加载类并调用defineClass方法生成Class对象
28 c = findClass(name);
29
30 // this is the defining class loader; record the stats
31 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
32 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
33 sun.misc.PerfCounter.getFindClasses().increment();
34 }
35 }
36 if (resolve) {
37 // 解析类
38 resolveClass(c);
39 }
40 return c;
41 }
42 }
自定义一个类加载器
集成ClassLoader自定义一个类加载器,我们设定加载的路径是:D:\dongzhi\Desktop。
1package io.dongzhi;
2import java.io.*;
3public class MyClassLoader extends ClassLoader {
4 private final String ext = ".class";
5 private String path;
6
7 public MyClassLoader(String path) {
8 this.path = path;
9 }
10
11 @Override
12 public String toString() {
13 return super.toString();
14 }
15
16 @Override
17 protected Class<?> findClass(String name) {
18 String fullPath = this.path + "/" + name.replace(".", "/") + ext;
19 try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
20 FileInputStream fis = new FileInputStream(new File(fullPath));) {
21 byte[] buffer = new byte[512];
22 int count = 0;
23 while ((count = fis.read(buffer)) != -1) {
24 bos.write(buffer, 0, count);
25 }
26 return super.defineClass(name, bos.toByteArray(), 0, bos.size());
27 } catch (FileNotFoundException e) {
28 e.printStackTrace();
29 } catch (IOException e) {
30 e.printStackTrace();
31 }
32 return null;
33 }
34
35 public static void main(String[] args) throws ClassNotFoundException {
36 MyClassLoader loader = new MyClassLoader("D:\\dongzhi\\Desktop"); // 自定义路径
37 Class<?> clazz = loader.loadClass("io.dongzhi.Person");
38 System.out.println("class:" + clazz);
39 System.out.println("calss loader:" + clazz.getClassLoader());
40 }
41}
- 当前项目中有Person这个类时,打印结果为:
1class:class io.dongzhi.Person
2calss loader:sun.misc.Launcher$AppClassLoader@18b4aac2
3
4Process finished with exit code 0
- 当前项目中没有Person这个类时,打印结果:
java.io.FileNotFoundException: D:\dongzhi\Desktop\io\dongzhi\Person.class (系统找不到指定的路径。) at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(FileInputStream.java:195) at java.io.FileInputStream.(FileInputStream.java:138) at io.dongzhi.MyClassLoader.findClass(MyClassLoader.java:21) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at io.dongzhi.MyClassLoader.main(MyClassLoader.java:38) class:null Exception in thread "main" java.lang.NullPointerException at io.dongzhi.MyClassLoader.main(MyClassLoader.java:40) Process finished with exit code 1
- 当前项目中没有Person这个类,但是D:/dongzhi/Desktop这个目录有Person.class这个类,打印结果如下:
1java.io.FileNotFoundException: D:\dongzhi\Desktop\io\dongzhi\Person.class (系统找不到指定的路径。)
2 at java.io.FileInputStream.open0(Native Method)
3 at java.io.FileInputStream.open(FileInputStream.java:195)
4 at java.io.FileInputStream.<init>(FileInputStream.java:138)
5 at io.dongzhi.MyClassLoader.findClass(MyClassLoader.java:21)
6 at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
7 at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
8 at io.dongzhi.MyClassLoader.main(MyClassLoader.java:38)
9class:null
10Exception in thread "main" java.lang.NullPointerException
11 at io.dongzhi.MyClassLoader.main(MyClassLoader.java:40)
12
13Process finished with exit code 1
- 当前项目中没有Person这个类,把Person.class放置到D:/dongzhi/Desktop/io/dongzhi/Person.class,打印结果如下:
1class:class io.dongzhi.Person
2calss loader:io.dongzhi.MyClassLoader@1540e19d
3
4Process finished with exit code 0
以上几个测试中,根据双亲委托机制,当MyClassLoader加载Person.class类时,先委托给父类加载器AppClassLoader,AppClassLoader加载的范围是当前项目的类路径,所以第一种情况中能加载成功,第二种情况中,项目类路径中没有Person类,自定义的类加载器就会起作用,会去加载D:/dongzhi/Desktop目录下的类,由于类不存在,所以报异常。第三、四种情况中,MyClassLoader加载类时,把包拆分成路径去加载类,所以类的正确路径应当是:D:/dongzhi/Desktop/io/dongzhi/Person.class。
命名空间
紧接着,修改测试用例,将main方法修改如下,定义2个类加载器:
1 public static void main(String[] args) throws ClassNotFoundException {
2 MyClassLoader loader = new MyClassLoader("D:\\dongzhi\\Desktop"); // 自定义loader1
3 Class<?> clazz = loader.loadClass("io.dongzhi.Person");
4 System.out.println("class:" + clazz + ",hashcode" + clazz.hashCode());
5 System.out.println("calss loader:" + clazz.getClassLoader());
6
7 MyClassLoader loader2 = new MyClassLoader("D:\\dongzhi\\Desktop"); // 自定义loader2
8 Class<?> clazz2 = loader2.loadClass("io.dongzhi.Person");
9 System.out.println("class2:" + clazz2 + ",hashcode" + clazz2.hashCode());
10 System.out.println("calss loader2:" + clazz2.getClassLoader());
11 }
接着测试以下情况:
- 当当前项目类路径中存在Person.class时,输出如下:
1class:class io.dongzhi.Person,hashcode356573597
2calss loader:sun.misc.Launcher$AppClassLoader@18b4aac2
3class2:class io.dongzhi.Person,hashcode356573597
4calss loader2:sun.misc.Launcher$AppClassLoader@18b4aac2
5
6Process finished with exit code 0
- 当当前项目类路径不存在Person类,而是存在于自定义加载器的类路径中时,即:D:/dongzhi/Desktop/io/dongzhi/Person.class。则输出如下:
1class:class io.dongzhi.Person,hashcode21685669
2calss loader:io.dongzhi.MyClassLoader@1540e19d1577875235796
3class2:class io.dongzhi.Person,hashcode325040804
4calss loader2:io.dongzhi.MyClassLoader@7f31245a1577875235797
5
6Process finished with exit code 0
分析: 定义了两个类加载器loader和loader1,它们的加载路径是一样的,第1种情况中,输出的结果显示,2个类加载器为同一个实例,加载的Class也为同一个实例,而第2种情况中,类加载器loader和loader1为2个不同的实例,加载的Person也是不同的实例。原因是什么呢?
第1种情况中,当前项目类路径存在Person类时,loader委托父类加载器AppClas正常加载成功,loader2委托AppClassLoader加载Person时,父类加载器发现Person类已经由其加载过了,直接从缓存中取出返回被加载的Person类。 第二种情况中,加载类的任务落在了loader和loader1身上,显然它们是不同的实例,类加载器不同,它们加载的类也是不同的实例。
由此可见,类加载器(以及所有的父类加载器)构成了被加载类的命名空间,同一个类加载器的实例,对同一个类只会加载一次,且缓存起来,不同类加载器的实例,加载同一个类时,得到的Class是不同的实例。
类的卸载
一个类的生命周期取决于对应的Class对象的生命周期,当该Class对象不可触及时,这个类在方法区中的数据就会被清除掉。
由JVM自带的类加载器所加载的类,在整个JVM生命周期中都不会被卸载。JVM本身会引用那些自带的类加载器,自带的类加载器又会引用它们所加载的类的Class对象,所以这些Class对象始终是可触及的。
用户自定义的类加载器加载的类是可以卸载的。
加上-XX:+TraceClassUnloading这个参数,在上面的例子中显式调用System.gc(),可以看到类的卸载日志。
参考资料
https://www.ibm.com/developerworks/cn/java/j-lo-classloader/ https://blog.csdn.net/m0_38075425/article/details/81627349