17-类加载器双亲委托机制实例深度剖析

类加载器双亲委托机制实例深度剖析

java.lang.ClassLoader
protected Class<?> loadClass(String name , 
                          boolean resolve) 
throws ClassNotFoundException

​ Loads the class with the specified binary name. The default implementation of this method searches for classes in the following order:

① Invoke findLoadedClass(String) to check if the class has already been loaded.
② Invoke the loadClass method on the parent class loader. If the parent is null the class loader built-in to the virtual machine is used, instead.
③ Invoke the findClass(String) method to find the class.

​ 使用指定的二进制名称加载类。此方法的默认实现按以下顺序搜索类:

​ ①调用findLoadedClass(String)检查类是否已加载。

​ ②在父类加载器上调用loadClass方法。如果父级为空,则使用虚拟机的内置类加载器。

​ ③调用findClass(String)方法来查找类。

​ If the class was found using the above steps, and the resolve flag is true, this method will then invoke the resolveClass(Class) method on the resulting Class object.

​ 如果使用上述步骤找到该类,并且resolve标志为true,则此方法将对生成的类对象调用resolveClass(Class)方法。

​ Subclasses of ClassLoader are encouraged to override findClass(String), rather than this method.
​ Unless overridden, this method synchronizes on the result of getClassLoadingLock method during the entire class loading process.

​ 鼓励类加载器的子类重写findClass(String),而不是此方法。

​ 除非重写,否则在整个类加载过程中,此方法将同步getClassLoadingLock方法的结果。

Parameters:
name - The binary name of the class
resolve - If true then resolve the class
Returns:
The resulting Class object
Throws:
ClassNotFoundException - If the class could not be found

献上源码:

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
                }

                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;
        }
    }
  • 对15节的自定义类加载继续分析:
    • 在(1)、(2)、(3)处分别加了打印语句,最终发现 (1) 、(2)没有被执行,只有(3)被执行。因为MyTest16 loader1 = new MyTest16(“loader1”);,是一个参数的构造函数, 这样 “将系统类加载器当做该类加载器的父加载器”,同时,系统类加载器又可以加载 MyTest16,所以,最终打印输出的语句中是:系统类加载,而不是我们自定义的类加载器。
    • 究其原因,根据类加载器的双亲委托机制,MyTest16(“loader1”)一定不是loader1这个类加载器率先尝试加载,而是委托给他的父亲尝试加载, 而loader1的父亲就是 “系统类加载器”。
    • 若有一个类加载器能够成功加载Test类,那么这个类加载器被称为定义类加载器,所有能成功返回Class对象应用的类加载器(包括自定义类加载器)都被称为初始类加载器。【这里的定义类加载器是:系统类加载器,初始类加载器为:系统类加载器和自定类加载(loader1)】
/**
 * 自定义类加载器
 */
public class MyTest16 extends ClassLoader{
    private String classLoaderName;       //标示性的属性,类加载器的名字
    private final String fileExtension = ".class";  //每一次中磁盘上读取的文件的扩展名
    public MyTest16(String classLoaderName){
        super();   //将系统类加载器当做该类加载器的父加载器
        this.classLoaderName = classLoaderName;
    }
    // 该方法的前提是已经有了parent这样一个加载器
    public MyTest16(ClassLoader parent, String classLoaderName){
        super(parent);   //显示执行该类加载器的父加载器(parent)
        this.classLoaderName = classLoaderName;
    }
    @Override
    public String toString() {
        return "[" + this.classLoaderName + "]";
    }

    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        System.out.println("findClass invoked:"+className);             //(1)
        System.out.println("class loader name:"+this.classLoaderName);	//(2)
       
        byte[] data = this.loadClassData(className);
        return this.defineClass(className,data,0,data.length); //最终返回一个字节class对象
    }

    /*
    	通过类的名字(className),把对应的文件的名字找到,并以输入输出流的形式最后返回一个字节数组,这个
    	字节数组就是从class文件中读取的二进制信息。
    */
    private byte[] loadClassData(String className){
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;
        try {
            this.classLoaderName = this.classLoaderName.replace(".","/");
            is = new FileInputStream(new File(className + this.fileExtension));
            int ch;
            while (-1 != (ch=is.read())){
                baos.write(ch);
            }
            data = baos.toByteArray();
        }catch (Exception ex){
            ex.printStackTrace();
        }finally {
            try {
                baos.close();
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return data;
    }

    public static void test(ClassLoader classLoader) throws Exception {
        //1.下面这个调用的loadClass方法,其实调用的还是ClassLoader类中的方法,
        //2.loadClass方法回去调用我们上面所重写的 findClass方法,根据二进制名字寻找
        //class对象(这里是MyTest10类)。 【这也是自定义类最关键的一环,重写findClass方法】
        Class<?> clazz = classLoader.loadClass("Jvm.MyTest10");
        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());  //(3)
    }

    public static void main(String[] args) throws Exception {
        MyTest16 loader1 = new MyTest16("loader1");
        test(loader1);
    }

}
运行结果:
      MyTest10 static block
      Jvm.MyTest10@6d6f6e28
      sun.misc.Launcher$AppClassLoader@18b4aac2
1. 使用自定义的类加载器,加载class文件:
  • 首先将MyTest10的class文件,在桌面创建一个文件夹Jvm放里面,然后删除编译生成的MyTest10.class文件。当程序运行时,loader1加载器(自定义加载器)首先委托双亲(系统类加载器)去加载,系统类加载器获取找classpath目录下对应的class文件,但是,因为class文件删除了,所以加载不成功,然后往上找双亲直到根类加载器,还是加载不成功,然后返回到loader1类加载器,由loader1去加载MyTest10.class
  • ★★如果★★编译生成的MyTest10.class文件不删除,那么将由系统类加载器去加载该class文件,【原因:有双亲委托机制,loader1类加载器会调用父类系统类加载器去尝试加载classpath文件夹下的class文件,这个时候,因为存在在classpath下存在class文件,所以可以加载。而,不会去加载桌面上的class文件】
/**
 * 自定义类加载器
 */
public class MyTest16 extends ClassLoader{
    private String classLoaderName;
    private String path;
    private final String fileExtension = ".class";
    public MyTest16(String classLoaderName){
        super();   //将系统类加载器当做该类加载器的父加载器
        this.classLoaderName = classLoaderName;
    }
    public MyTest16(ClassLoader parent, String classLoaderName){
        super(parent);   //显示执行该类加载器的父加载器(parent)
        this.classLoaderName = classLoaderName;
    }
    public void setPath(String path) {
        this.path = path;
    }

    @Override
    public String toString() {
        return "[" + this.classLoaderName + "]";
    }

    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        System.out.println("findClass invoked:"+className);
        System.out.println("class loader name:"+this.classLoaderName);

        byte[] data = this.loadClassData(className);
        return this.defineClass(className,data,0,data.length); //最终返回一个class对象
    }

    private byte[] loadClassData(String className){
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        className = className.replace(".","\\");
        try {
            is = new FileInputStream(new File(this.path + className + this.fileExtension));
            baos = new ByteArrayOutputStream();
            int ch;
            while (-1 != (ch = is.read())){
                baos.write(ch);
            }
            data = baos.toByteArray();
        }catch (Exception ex){
            ex.printStackTrace();
        }finally {
            try {
                is.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }
    public static void main(String[] args) throws Exception {
        MyTest16 loader1 = new MyTest16("loader1");
        loader1.setPath("C:\\Users\\admin\\Desktop\\");
		  //这里是将MyTest10的class文件,在桌面创建一个文件夹Jvm放里面,去加载桌面文件夹下的class文件
        Class<?> clazz = loader1.loadClass("Jvm.MyTest10");
        System.out.println("class:"+clazz.hashCode());
        Object object = clazz.newInstance();
        System.out.println(object);
    }
}
运行结果:
      findClass invoked:Jvm.MyTest10 <--- 说明调用了自定义的类加载器loader1去加载MyTest10.class
      class loader name:loader1		 <---
      class:1173230247
      MyTest10 static block
      Jvm.MyTest10@330bedb4
  • 实例二:
    • 前提:classpath中存在MyTest10.class文件,从最终的运行结果来看,创建了两个类加载器,第一个类加载器加载完MyTest.class之后,当第二次加载同样的类(MyTest.class)的时候,就不会加载,因为源码中应该有判断,如果该类已经被加载了,那么直接返回该对象即可。所以,对应返回的hashcode()相同,且MyTest10 static block只执行了一次。

    • 但是,如果classpath中的MyTest10.class文件删除了,运行结果为下面所示,MyTest10类其实还是被加载了一次,【一个类只被加载一次定义并没有被打破,因为可以看到MyTest10类中的静态代码块MyTest10 static block只被执行了一次,说明MyTest10 只被加载一次】,但是可以看到,class输出的hashcode()不同,这里涉及到命名空间问题。

      • 命名空间:
        • 每一个类加载器都有自己的命名空间,命名空间由该类加载器及所有父类加载器所加载的类组成
        • 在同一个命名空间中,[ 不会出现类的完整名字(包括类的包名)相同的 ]两个类。
        • 在不同的命名空间中,[ 有可能会出现类的完整名字(包括类的包名)相同的 ]两个类。
      findClass invoked:Jvm.MyTest10
      class loader name:loader1
      class:1173230247
      MyTest10 static block   //第一加载,并初始化,静态代码块被执行力。
      Jvm.MyTest10@330bedb4
      
      findClass invoked:Jvm.MyTest10
      class loader name:loader2
      class:2125039532
      Jvm.MyTest10@12a3a380
      
//这里只写了主函数
public static void main(String[] args) throws Exception {
      MyTest16 loader1 = new MyTest16("loader1");
      loader1.setPath("C:\\Users\\admin\\Desktop\\");

      Class<?> clazz = loader1.loadClass("Jvm.MyTest10");
      System.out.println("class:"+clazz.hashCode());
      Object object = clazz.newInstance();
      System.out.println(object);

      System.out.println();

      MyTest16 loader2 = new MyTest16("loader2");
      loader1.setPath("C:\\Users\\admin\\Desktop\\");

      Class<?> clazz1 = loader2.loadClass("Jvm.MyTest10");
      System.out.println("class:"+clazz1.hashCode());
      Object object1 = clazz.newInstance();
      System.out.println(object1);
}
运行结果:
   class:1836019240            <--
   MyTest10 static block		 <--	
   Jvm.MyTest10@135fbaa4

   class:1836019240				 <--
   Jvm.MyTest10@45ee12a7
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值