深入了解java虚拟机(JVM) 第十二章 类加载器

时间:2021-10-06 14:33:21

一、什么是类加载器

  类加载器是一个用来加载类文件的类,Java源代码通过javac编译器编译成类文件,然后JVM来执行类文件中的字节码来执行程序。需要注意的是,只有被同一个类加载器加载的类才可能会相等。相同字节码被不同的类加载器加载的类不相等。

二、类加载器分类

  1.启动类加载器

    由C++实现,是虚拟机的一部分,用于加载javahome下的lib目录下的类;

   2.扩展类加载器

    加载javahome下/lib/ext目录中的类;

   3.应用程序类加载器

    加载用户类路径上的所指定的类库,也就是我们所用的类加载器;

三、自定义加载器

  在jvm中,除了以上三种类加载器外,我们还可以自定义加载器,自定义加载器的方法有三步

    1.定义一个类继承classloader

    2.重写loadClass方法

    3.实例化class对象

   我们看下面的例子: 

深入了解java虚拟机(JVM) 第十二章 类加载器深入了解java虚拟机(JVM) 第十二章 类加载器
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();
            }
    }
    
    
}
    
    

        
            
        
          
View Code

 

  上面我们已经完成了一个自定义类加载器,接下来使用一个方法来测试

 

深入了解java虚拟机(JVM) 第十二章 类加载器深入了解java虚拟机(JVM) 第十二章 类加载器
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);
    }
}
View Code

 

 

  测试的结果为:

深入了解java虚拟机(JVM) 第十二章 类加载器

  为什么obj1不是TestMain的实例?这就是回来我们一开始说道的,相同的字节码被不同的类加载器加载的类不相等。

 四、自定义加载器的优势

  1.高度的灵活性;

  2.通过自定义类加载器可以实现热部署

  3.代码加密 

 五、类加载器之间的协同工作--双亲委派模型

  在jvm中有的各种类加载器,他们之间是通过双亲委派模型的类加载机制进行协同工作,如图:

  深入了解java虚拟机(JVM) 第十二章 类加载器

 

双亲委派模型的工作原理主要是:

  1)如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器完成。

  2)每一层的类加载器都把类加载请求委派给父类加载器,直到所有的类加载请求都应该传递给顶层的启动类加载器。

  3)如果顶层的启动类加载器无法完成加载请求,子类加载器尝试去加载,如果连最初发起类加载请求的类加载器也无法完成加载请求时,将会抛出classNotFoundException,而不再调用其子类加载器去进行类加载。

双亲委派模式的类加载机制的优点:

  java类它的类加载器一起具备了一种带优先级的层次关系,越是基础的类,越是被上层的类加载器进行加载,保证了java程序的稳定运行。