为什么JVM需要多种类加载器

JVM的类加载器

刚刚学习JVM的类加载机制的时候,会被教育说JVM的类加载机制需要不同的类加载器。过了很久之后忘记了。现在再复习一下。

为什么需要多个类加载器?
加载器作用是通过类名来获取二进制字节流。

我们先抛开所有问题,从写程序的角度来讲一个程序应该具有什么?
1、健壮性
2、功能性
3、鲁棒性
4、效率性
5、维护性
6、可靠性(安全性)

由此,我们对比JVM。JVM也是一个软件,也应该基本符合上述的几个特性。
健壮性和功能性:JVM可以从不同的地方去加载class,比如文件系统,web,FTP等,这就要求JVM屏蔽底层的加载逻辑,只需要提供一个classloard()的接口就行了,客户端就可以加载类但是却不用管类加载器到底是怎么实现的。
再说安全性:JVM得保证自有类不遭到破坏(比如java.lang包下的类,这个破坏的原理就是类对同包级别及其子包下的public类具有操作权限)。为了解决这个问题,使用双亲委派机制刚好可以解决。

此外,如果我想在一个JVM中启动两个相同的应用的不同版本(不同版本值得是同样的全限定名的类,但是功能不一样),怎么办?假设只有一个类加载器,那么第二次加载该类会失败,第二个版本的应用还是使用了第一个版本的class,那么第二个应用就无法提供第二个版本类的功能了。所以得需要每个应用都有相互隔离的类加载器,否则第二个应用的类可能会覆盖第一个应用之前加载的类,从而造成一些意想不到的后果。

每种加载器都有对应的层级来加载某些特定的类,来保证他们之间的安全性。

自定义的类加载器。

package com.stat;

import java.util.Date;

public class MyDate extends Date {
    @Override
    public String toString() {
        return "MyDate : 我的时间。";
    }
}
package com.stat;

import java.io.*;

/**
 * 自定义类加载器
 */
public class MyClassLoader extends ClassLoader {
    String classDir;

    public MyClassLoader() {
    }

    public MyClassLoader(String classDir) {
        this.classDir = classDir;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String classFile = classDir + "/" + name + ".class";
        System.out.println("classFile path==" + classFile);
        try {
            //这个地方我们只是简单的读取文件流的方式来获取byte数组
            //其实可以尝试将class文件加密以后 这里解密 这样就可以保证
            //这种class文件 只有你写的classloader才能读取的了。
            //其他任何classloader都读取不了 包括系统的。
            byte[] classByte = toByteArray(classFile);
            return defineClass(classByte, 0, classByte.length);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    public static byte[] toByteArray(String filename) throws IOException, FileNotFoundException {

        File f = new File(filename);
        if (!f.exists()) {
            throw new FileNotFoundException(filename);
        }

        try (ByteArrayOutputStream bos = new ByteArrayOutputStream((int) f.length())) {
            BufferedInputStream in = null;
            in = new BufferedInputStream(new FileInputStream(f));
            int bufSize = 1024;
            byte[] buffer = new byte[bufSize];
            int len = 0;
            while (-1 != (len = in.read(buffer, 0, bufSize))) {
                bos.write(buffer, 0, len);
            }
            return bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            throw e;
        }
    }
}

package com.stat;

import java.util.Date;

public class MyClassTest {
    public static void main(String[] args)
    {
        try {
            //注意这个路径:是在target目录下的,是编译后的路径
            Class classDate = new MyClassLoader("D:\\javaworkspace\\test02\\test02_module01\\target\\classes\\com\\stat").loadClass("com.stat.MyDate");
            Class classDate2 = new MyClassLoader("D:\\javaworkspace\\test02\\test02_module01\\target\\classes\\com\\stat").loadClass("MyDate");
            Date date = (Date) classDate.newInstance();
            System.out.println("date ClassLoader:"+date.getClass().getClassLoader().getClass().getName());
            System.out.println(date);

            Date date2 = (Date) classDate2.newInstance();
            System.out.println("date2 ClassLoader:"+date2.getClass().getClassLoader().getClass().getName());
            System.out.println(date2);
        } catch (Exception e1) {
            e1.printStackTrace();
        }

    }

}

输出结果如下

classFile path==D:\javaworkspace\test02\test02_module01\target\classes\com\stat/MyDate.class
date ClassLoader:sun.misc.Launcher$AppClassLoader
MyDate : 我的时间。
date2 ClassLoader:com.stat.MyClassLoader
MyDate : 我的时间。

可以看到date 的类加载器是AppClassLoader,而date2的类加载器是自定义的

大家可以看到classdate和classDate2 这2个类,我们在用classLoader去加载的时候传的参数唯一的不同就是前者传入了完整的包名,而后者没有。这就导致了前者的classLoader依旧是系统自带的appclassloader 而后者才是我们自定义的classloader。 原因:

虽然对于classDate和classDate2来说,我们手动指定了她的类加载是我们自定义的myclassloader,但是根据类加载器的规则,我们能用父亲的loadclass就肯定不会用自己的,而我们系统类加载器,AppClassLoader要想loadclass成功是需要传入完整的包名的。所以classDate的构造还是传入了完整的包名,这就是为啥classDate的加载器还是AppClassLoader,但是classDate2并没有传入完整的包名,所以AppClassLoader也是找不到这个CustomDate类的,最后只能交给MyClassLoader这个最底层的,我们自定义的classloader来load

posted @ 2021-06-28 23:04  头上有多云  阅读(561)  评论(0编辑  收藏  举报