深入了解java虚拟机(JVM) 第十二章 类加载器
一、什么是类加载器
类加载器是一个用来加载类文件的类,Java源代码通过javac编译器编译成类文件,然后JVM来执行类文件中的字节码来执行程序。需要注意的是,只有被同一个类加载器加载的类才可能会相等。相同字节码被不同的类加载器加载的类不相等。
二、类加载器分类
1.启动类加载器
由C++实现,是虚拟机的一部分,用于加载javahome下的lib目录下的类;
2.扩展类加载器
加载javahome下/lib/ext目录中的类;
3.应用程序类加载器
加载用户类路径上的所指定的类库,也就是我们所用的类加载器;
三、自定义加载器
在jvm中,除了以上三种类加载器外,我们还可以自定义加载器,自定义加载器的方法有三步
1.定义一个类继承classloader
2.重写loadClass方法
3.实例化class对象
我们看下面的例子:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.example.demo; import java.io.InputStream; public class Test1 extends ClassLoader{ @Override public Class<?> loadClass(String name) throws ClassNotFoundException { //name的值为com.example.demo.Test1,他是类的绝对路径 //截取name后 fileName的值为Test1.class //加上.class表示这是个class文件 String fileName=name.substring(name.lastIndexOf(".")+1)+".class"; //加载这个class文件 InputStream input=getClass().getResourceAsStream(fileName); //判断input是否为空 //为空就证明当前文件夹下没有这个文件 //如果为空就让父类加载器去加载它 if (input==null) { return super.loadClass(name); } //如果不为空,就用当前的类加载器进行加载 try { //简单的IO流操作,用创建一个byte数组,然后将输入流输入数组 byte [] buff=new byte[input.available()]; input.read(buff); //方便测试,我们加上一行代码 System.out.println("自定义类加载器启动"); //当读取后,我们需要实例化class对象 //在这里我们使用java为我们提供的defineClass方法实例化对象 //defineClass的参数意思:要加载类的绝对路径,读取的数组,从第几位开始读,读到第几位结束 return defineClass(name, buff, 0, buff.length); } catch (Exception e) { throw new ClassNotFoundException(); } } }
上面我们已经完成了一个自定义类加载器,接下来使用一个方法来测试
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.example.demo; public class TestMain { public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Test1 test1=new Test1(); //使用反射,用自定义类加载器加载并创建一个实例会对象 Object obj1=test1.loadClass("com.example.demo.TestMain").newInstance(); //使用反射,用系统默认的类加载器加载并创建一个实例会对象 Class<?> cls = Class.forName("com.example.demo.TestMain"); Object obj2=cls.newInstance(); System.out.println(obj1.getClass()); //判断obj1是否是TestMain类的实例 boolean b1=obj1 instanceof TestMain; System.out.println("obj1是不是TestMain的实例:"+b1); //判断obj2是否是TestMain类的实例 boolean b2=obj2 instanceof TestMain; System.out.println("obj2是不是TestMain的实例:"+b2); } }
测试的结果为:
为什么obj1不是TestMain的实例?这就是回来我们一开始说道的,相同的字节码被不同的类加载器加载的类不相等。
四、自定义加载器的优势
1.高度的灵活性;
2.通过自定义类加载器可以实现热部署
3.代码加密
五、类加载器之间的协同工作--双亲委派模型
在jvm中有的各种类加载器,他们之间是通过双亲委派模型的类加载机制进行协同工作,如图:
双亲委派模型的工作原理主要是:
1)如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器完成。
2)每一层的类加载器都把类加载请求委派给父类加载器,直到所有的类加载请求都应该传递给顶层的启动类加载器。
3)如果顶层的启动类加载器无法完成加载请求,子类加载器尝试去加载,如果连最初发起类加载请求的类加载器也无法完成加载请求时,将会抛出classNotFoundException,而不再调用其子类加载器去进行类加载。
双亲委派模式的类加载机制的优点:
java类它的类加载器一起具备了一种带优先级的层次关系,越是基础的类,越是被上层的类加载器进行加载,保证了java程序的稳定运行。