类加载器(深入理解Java虚拟机笔记)

概述

在类加载阶段中,通过一个类的全限定名来来获取描述该类的二进制字节流 这个操作放在Java虚拟机中实现,实现这个操作的代码被称为类加载器。

 


类与加载器

对于任意一个类,都必须由加载它的类加载器和和这个类本身 来确定它在虚拟机中的唯一性。每一个类加载器,都拥有一个独立的类名称空间,比较两个类是否相等,只有在两个类是由同一个类加载器 加载的前提下才有意义,否则,即便这两个类来源于同一个 Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

此处说的相等 包括equals()方法、isAssignableFrom()方法、isInstance() 方法的返回结果,也包括了使用instanceof关键字做对象所属关系判定等各种情况。

双亲委派模型

从Java开发人员的角度来看,Java保持着三类加载器,双亲委派的类加载架构。

(1)启动类加载器:这个类加载器负责加载存放在 \lib目录,或者被-Xbootclasspath参数所指定的路径中存放的 ,而且是Java虚拟机能够识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机的内存中。

启动类加载器无法被Java程序直接引用,我们在编写自定义类加载器时,如需要把加载请求委派给启动类加载器,则直接使用null代替。下面是java.lang.ClassLoader.getClassLoader()方法

public ClassLoader getClassLoader() {
        ClassLoader cl = getClassLoader0();
        if (cl == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            ClassLoader ccl = ClassLoader.getCallerClassLoader();
            if (ccl != null && ccl != cl && !cl.isAncestor(ccl)) {
                sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
            }
        }
        return cl;
 }

 

(2)扩展类加载器:该类加载器是在类sun.misc.Launcher$ExtClassLoader 中以Java代码的形式实现的,它负责加载载\lib\ext目录中,或者被java.ext.dirs系统变量所 指定的路径中所有的类库。 开发者可以直接在程序中使用扩展类加载器来加载Class文件。

(3)应用程序类加载器:这个类加载器由 sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径上所有的类库,开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

JDK9之前的Java应用都是由上述三类加载器互相配合完成加载的,若用户认为有必要,还可加入自定义的类加载器来进行拓展。这些类加载器之间的协作关系如下图所示,这种层次关系被称为类加载器的“双亲委派模型“。

双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载 器。不过这里类加载器之间的父子关系一般不是以继承的关系来实现的,而是通常使用组合关系来复用父加载器的代码。

双亲委派模型的工作过程

如果一个类加载器收到了类加载的请求,首先把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请 求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。

使用双亲委派模型来组织类加载器之间关系的好处之一就是:Java中的类随着它的类加载器一起具备了一种带优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一 个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载因此Object类 在程序的各种类加载器环境中都能够保证是同一个类;若没有使用此模型,而是都有各个类加载器自行去加载的话,如果用户自己也编写了一个名为java.lang.Object的类,并放在程序的 ClassPath中,那系统中就会出现多个不同的Object类,Java类型体系中最基础的行为也就无从保证,应用程序将会变得一片混乱。(自己编写的可以正常编译,但永远无法被加载运行)

双亲委派模型的代码实现

    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
// 首先,检查请求的类是否已经被加载过了
        Class c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
// 如果父类加载器抛出ClassNotFoundException
// 说明父类加载器无法完成加载请求
            }
            if (c == null) {
// 在父类加载器无法加载时
// 再调用本身的findClass方法来进行类加载
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }

 


自定义类加载器

自定义的类加载器需要继承ClassLoader,并覆盖findClass方法。如下代码所示,它首先根据类的全名在文件中查找累的字节代码文件(.class文件),然后读取文件内容,通过 defineClass() 方法来把这些字节代码转换成 java.lang.Class 类的实例。

 

public class People {       //该类写在记事本里,保存为.java文件。在用javac命令行编译成class文件,放在F盘下
    private String name;

    public People() {}

    public People(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String toString() {
        return "I am a people, my name is " + name;
    }

}
public class MyClassLoader extends ClassLoader {
    public MyClassLoader() { }
    public MyClassLoader(ClassLoader parent) {
        super(parent);
    }

    private byte[] getClassBytes(File file) throws Exception {
        //读入.class字节
        FileInputStream fis = new FileInputStream(file);
        FileChannel fc = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel wbc = Channels.newChannel(baos);
        ByteBuffer bbu = ByteBuffer.allocate(1024);

        while (true) {
            int len = fc.read(bbu);
            if(len == 0 || len == -1)
                break;
            bbu.flip();
            wbc.write(bbu);
            bbu.clear();
        }
        fis.close();
        return  baos.toByteArray();
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File file = new File("F:/People.class");
        try {
            byte[] bArray = getClassBytes(file);
//            defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
            Class<?> cl = this.defineClass(name, bArray, 0, bArray.length);
            return cl;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    public static void main(String[] args) throws Exception{
        MyClassLoader mcl = new MyClassLoader();
        Class<?> cl = Class.forName("People", true, mcl);
        Object obj = cl.newInstance();

        System.out.println(obj);
        System.out.println(obj.getClass().getClassLoader());
    }
}

 

java.lang.ClassLoader 的 loadClass() 实现了双亲委派模型的逻辑,自定义类加载器一般不去重写它

 


上文代码参选自:

https://blog.csdn.net/SEU_Calvin/article/details/52315125

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章