JVM学习笔记(2)之类加载器双亲委托机制实例

JVM学习笔记(2)之类加载器双亲委托机制实例

1、什么是类加载器的双亲委托机制

类的加载器分为三种,也可以自定义,分别为Bootstrap Class Loader(启动加载器)、Extensions Class Loader(扩展加载器)、Application Class Loader(系统(App)加载器)

  • 启动加载器:

    它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar 或 sun.boot.class.path 路径下的内容),是用原生代码来实现的(C++),并不继承自java.lang.ClassLoader

  • 扩展加载器:

    用来加载Java的扩展库(JAVA_HOME/jre/lib/ext/*.jar或 java.ext.dirs 路径下的内容) .java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类

  • 系统加载器:

    它根据 Java 应用的类路径(ClassPath, java.class.path)来加载。
    一般来说,Java应用的类都是由它来完成加载的

双亲委托机制:

所谓双亲委托机制指的是当Class文件被加载的时候往往是先交给上级加载器加载,如果上级加载器仍然存在上级加载器则继续往上传递,当上级加载器无法加载Class文件的时候才依次往下传递,简单点说就是先往上传递,再根据当前加载器能否加载判断是否往下传递,如果传递到最下面仍然无法加载,则虚拟机加载失败,程序退出,它们的关系如下图

在这里插入图片描述
(图片来源于网络)

自定义类加载器:

package com.zh.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

/**
 * @ClassName:MyClassLoader
 * @Description:TODO
 * @author:Jerry
 * @Date 2019/10/21 12:55
 * @Version 1.0.2
 **/
public class MyClassLoader0 extends ClassLoader {

    /**
     * 自定义加载器的名称
     */
    private String classLoaderName;

    //文件扩展名
    private static final String FILE_EXTENSTION = ".class";

    public MyClassLoader0(String classLoaderName) {
        //这里的super加与不加无影响?
        super();
        this.classLoaderName = classLoaderName;
    }

    public MyClassLoader0(String classLoaderName, ClassLoader parent) {
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    @Override
    public String toString() {
        return "MyClassLoader{" +
                "classLoaderName='" + classLoaderName + '\'' +
                '}';
    }

    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        System.out.println("findClass");
        byte[] bytes = loadClassData(className);
        return defineClass(className, bytes, 0, bytes.length);
    }

    private byte[] loadClassData(String className) {
        System.out.println("执行了 loadClassData");
        className = className.replace(".", File.separator);
        byte[] result = null;
        try (InputStream is = new FileInputStream(new File(className + FILE_EXTENSTION)); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[256];
            int len = 0;
            while ((len = is.read(buffer, 0, buffer.length)) != -1) {
                bos.write(buffer, 0, len);
            }
            result = bos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public static void testClassLoader(ClassLoader classLoader) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class<?> clazz = classLoader.loadClass("com.zh.classloader.Demo11");
        Demo11 o = (Demo11) clazz.newInstance();
        System.out.println("加载的实例对象:" + o);
        System.out.println("classloader:" + clazz.getClassLoader());
    }

    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        MyClassLoader loader1 = new MyClassLoader("loader1");
        testClassLoader(loader1);
    }
}

打印结果并没有我们想要的:执行了 loadClassData,证明并没有执行我们覆写的findClass()方法,如图.

在这里插入图片描述

其实原因很简单:自定的类加载器会寻找它的双亲加载器即系统加载器去加载,系统加载器再去委托上级,只不过它的上级都无法加载,往下重新传到系统加载器,系统加载器成功加载了Demo1.class文件,所以打印的就不是我们自定义的加载器

对代码进行改动:

package com.zh.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

/**
 * @ClassName:MyClassLoader
 * @Description:TODO
 * @author:Jerry
 * @Date 2019/10/21 12:55
 * @Version 1.0.2
 **/
public class MyClassLoader extends ClassLoader {

    /**
     * 自定义加载器的名称
     */
    private String classLoaderName;

    /**
     * 字节码文件路径
     */
    private String path;

    //文件扩展名
    private static final String FILE_EXTENSTION = ".class";

    public MyClassLoader(String classLoaderName) {
        //这里的super加与不加无影响?
        super();
        this.classLoaderName = classLoaderName;
    }

    public MyClassLoader(String classLoaderName, ClassLoader parent) {
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    public void setPath(String path) {
        this.path = path;
    }

    @Override
    public String toString() {
        return "MyClassLoader{" +
                "classLoaderName='" + classLoaderName + '\'' +
                '}';
    }

    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        System.out.println("findClass");
        byte[] bytes = loadClassData(className);
        return defineClass(className, bytes, 0, bytes.length);
    }

    private byte[] loadClassData(String className) {
        System.out.println("loadClassData");
        className = className.replace(".", File.separator);
        byte[] result = null;
        try (InputStream is = new FileInputStream(new File(path + className + FILE_EXTENSTION)); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
//            classLoaderName = classLoaderName.replace("\\", ".");
            byte[] buffer = new byte[256];
            int len = 0;
            while ((len = is.read(buffer, 0, buffer.length)) != -1) {
                bos.write(buffer, 0, len);
            }
            result = bos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

//    public static void testClassLoader(ClassLoader classLoader) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//        Class<?> clazz = classLoader.loadClass("com.zh.classloader.Demo1");
//        Object o = clazz.newInstance();
//        System.out.println(o);
//    }

    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        MyClassLoader loader1 = new MyClassLoader("loader1");
//        loader1.setPath("D:\\jvm_study\\target\\classes\\");
        loader1.setPath("C:\\Users\\Jack\\Desktop\\");
        Class<?> clazz = loader1.loadClass("com.zh.classloader.Demo11");
        System.out.println("classloader:"+clazz.getClassLoader());
        System.out.println("class:" + clazz.hashCode());
        Object o = clazz.newInstance();
        System.out.println(o);
    }
}

说明:只是加了个路径,为的是改变class文件的地址,我们知道系统加载器加载的是classpath目录以及我们编写的类的目录,所以认为改动它,我们将class文件拷贝到桌面上的指定目录下,然后加载器去加载,结果如下.

在这里插入图片描述

这跟我们想象的结果不一样,好奇怪?好的,我们来分析下原因,我们分析下findClass是什么时候执行的,是在loadClass的时候,如下图

在这里插入图片描述

所以其实并未执行到findClass,如果想要让加载器加载到我们放到桌面上的class文件,可以把我们项目目录中生成的class文件删除掉,此时,系统加载器在目录下并不能找到class文件,加载失败,交给我们自定义的加载器,然后在执行findClass的时候,修改了class文件的目录,我们自定义的classloader去桌面的目录下查找,查找到了,应该能执行成功了吧,现在我删除掉了目录下的class文件,如图
在这里插入图片描述

执行,结果如下:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值