JVM类加载器

在这里插入图片描述

前言

顾名思义,类的加载器就是负责类的加载职责,每一个class都需要由它的类加载器和这个类本身确立其在JVM中的唯一性,这也就是运行时包。

JVM中内置的三大类型加载器

JVM提供了三种累加器,分别是BootStrap Class LoaderExt Class LoaderApplication ClassLoader,不同的类加载器负责不同的类加载到JVM内存之中,并且它们之间严重遵循着父委托机制。

根类加载器介绍

根类加载器也称Bootstrap 加载器,它是由C++编写的,主要负责虚拟机核心类库的加载,比如整合java.lang包就是根类加载器加载的。

根类加载器是获取不到引用的,比如你想获取String.class.getClassLoader()时,程序会返回null

扩展类加载器

扩展类加载器的父加载器时根类加载器,它主要加载JAVA_HOME下的jre/lib/ext子目录下的类库。该加载器是纯JAVA语言编写的。可以通过系统属性java.ext.dirs获得。

可以将自己打好的jar放到扩展类加载器的路径下面,然后获取jar包中类的加载器,就是扩展类加载器。

系统类加载器

系统类加载器的父加载器是扩展类加载器,加载的是项目中classpath下的jar,同时可以通过-cp命令指定加载的路径,我们自定义的加载器的父类也是系统类加载器,
可以通过系统命令java.class.path获取加载路径。

自定义类加载器

所有自定义类加载器都是ClassLoader的子类或者间接子类,java.lang.ClassLoader是一个抽象类,它里面没有抽象方法,但是有findClass方法,务必实现该方法,否则会抛出Class找不到的异常,示例代码如下:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException{
 }

自定义类加载器,问候世界

public class MyClassloader extends ClassLoader {

    private final static Path DEFAULT_CLASS_DIR = Paths.get("C:", "classload1");

    private final Path classDir;

    public MyClassloader() {
        this.classDir = DEFAULT_CLASS_DIR;
    }

    public MyClassloader(String classDir) {
        super();
        this.classDir = Paths.get(classDir);
    }

    public MyClassloader(String classDir, ClassLoader parent) {
        super(parent);
        this.classDir = Paths.get(classDir);
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        //设置class的二进制数据
        byte[] classBytes = this.readClassBytes(name);
        if (null == classBytes || classBytes.length == 0) {
            throw new ClassNotFoundException("can not load this class" + name);
        }
        //调用defineClass方法定义class
        return this.defineClass(name, classBytes, 0, classBytes.length);
    }

    private byte[] readClassBytes(String name) throws ClassNotFoundException {
        String classPath = name.replace(".", "/");
        Path classFullPath = classDir.resolve(Paths.get(classPath + ".class"));
        if (!classFullPath.toFile().exists()) {
            throw new ClassNotFoundException("not fount class:" + name);
        }
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            Files.copy(classFullPath, baos);
            return baos.toByteArray();
        } catch (IOException e) {
            throw new ClassNotFoundException("this class" + name + "error:" + e);
        }
    }

    @Override
    public String toString() {
        return "MyClass Loader";
    }
}

以上就是自定义的类加载器,通过类的全名称转换为文件的全路径重写findClass方法,然后读取class文件的字节流,最后用ClassLoader的defineClass方法对类进行了定义。

下面写一个简单的程序,说明下

public class MyClassLoaderTest {

    public static void main(String[] args)
        throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        MyClassloader myClassloader = new MyClassloader();
        Class<?> clazz = myClassloader.loadClass("fast.cloud.nacos.juc.classloader.HelloWorld");
        System.out.println(clazz.getClassLoader());
        //1
        Object helloWorld = clazz.newInstance();
        System.out.println(helloWorld);
        Method welcomeMethod = clazz.getMethod("welcome");
        Object result = welcomeMethod.invoke(helloWorld);
        System.out.println("Result: " + result);
    }
}

具体操作步骤:

在这里插入图片描述
编译HelloWorld文件,注意带上 -d .会同时把包路径也生成好的。
在这里插入图片描述
删除HelloWorld.java文件
在这里插入图片描述
运行MyClassLoaderTest文件

在这里插入图片描述
如果没有错误发生,程序会输出类加载器的以及对世界的问候,注释掉1下面的代码,输出类加载器的信息,但是Hello World的静态代码块并没有输出,类加载loadClass并不会导致类的初始化。(这里有个面试题,用Class.forName这个方法会导致初始化么?这个问题以后的博客中回答,暂时留个伏笔

注意一定是重写findClass方法,切记切记。

双亲委托机制详细介绍

当一个类加载器调用了loadClass之后,它并不会直接将其加载,而是先交给当前类加载器的父加载器直到最顶层的父加载器,然后再依次向下进行加载。

之前我们担心HelloWorld.class文件被系统类加载器,所以删除了HelloWorld文件,那有什么方法可以不删除HelloWorld文件使用MyClassLoader进行加载呢?


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;
        }
    }
  • 从当前类加载器的加载缓存中根据类的全路径名查询是否存在该类,如果存在,则直接返回。
  • 如果当前类存在父类加载器,则调用父类加载器的loadClass方法对其进行加载。
  • 如果当前类加载器不存在父类加载器对该类进行加载
  • 如果当前类的所有父类加载器都没有成功加载class,则尝试调用当前类加载器的findClass方法进行加载,该方法就是我们自定义加载器所需要重写的方法。
  • 最后如果loadClass制定了resolve为false,所以不会进行连接阶段的继续执行,这也就解释了为什么通过类的加载器不会导致类的初始化。

看了源码之后,有两种方法可以在不删除HelloWorld文件,使用我们自定义的MyClassLoader进行加载。

  • 直接将扩展类加载器作为MyClassLoader的父加载器
  • 将MyClassLoader的父加载器设置为null

这个读者可以自行尝试,有更好的实现方案可以放到下方评论。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值