JVM(六):探究类加载过程-下

JVM(六):探究类加载过程-下

上文说了类加载过程的5个阶段,着重介绍了各个阶段作的工做。在本文中,咱们对执行加载阶段的主体进行探讨,学习类加载器的模型和逻辑,以及咱们该如何自定义一个类加载器。java

定义

前面说过加载阶段是一个可让设计人员高度自控的模块,由于类文件的源头能够是多种多样的,代码生成、反射生成或从网络中生成等。所以类加载器做为对这些文件的处理就显得尤其重要。数组

但类加载器的功能不只如此,其还有一个重要的功能就是和一个类的全限定名惟一肯定一个类。通俗来讲,要说两个类是相同的,不只其全限定名要同样,其对应的类加载器也必须相同,才能说明两个类是相等的。网络

正由于类加载器的功能角色如此重要,所以虚拟机对其的实现规范也十分重视。在Java虚拟机中,对其的实现模型是双亲委派模型jvm

模型

双亲委派模型

双亲委派模型的主要执行过程示意图如上所示,其分为启动类加载器(Bootstrap Class-loader),拓展类加载器(Extension Class-loader),应用程序类加载(Application Class-loader)。ide

其中启动类加载器主要负责加载 JRE 的核心类库,如 JRE 目录下的 rt.jar。但其实根据《深刻分析 Java Web 技术内幕》上所说,启动类加载器并不严格符合双亲委派模型,由于Bootstrap Class-loader 并不属于 JVM 的类等级层次。Bootstrap Class-loader 是没有子类的,Extension Class-loader 也是没有父类的。不过在这里咱们并不深究,只要知道有这一点就能够了。源码分析

Extension Class-loader 主要负责加载 JRE 拓展目录 ext 下的类。布局

Application Class-loader 主要负责用户类路径(Class-path)下的类,这个类加载器是使用的最多的,由于大大多数状况下,通常开发者并无实现自定义的类加载器,那么 JVM 就会使用这个来加载类大部分类。学习

执行过程

双亲委派模型执行过程

上图就是双亲委派模型的执行过程,当类开始加载的时候,先检查是否已经被加载过,若是没有被加载过,则调用父类的加载方法,若是父类加载失败,抛出异常,则调用自身的 findClass() 方法进行加载。this

JDK 中加载过程的源码分析:url

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 {
                    // 无父亲就调用 bootstarp 加载器来加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
        // 父加载器和 bootstarp 加载器都没有找到指定类,调用当前类的 findClass() 来完成类加载
        // 所以,自定义类加载器,就是重写 findClass() 方法
        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;
}
}

从源码中,咱们能够看到其实符合规范要求的 双亲委派模型 的。而当咱们要自定义一个类加载器的时候就是经过重写 findClass() 来实现的。

自定义类加载器

/**
 * 1. 自定义类加载器经过集成ClassLoader来实现,主要经过重写findClass方法
 * 2. findClass方法首先经过自定义的loadByte()方法将Class文件转换成byte[]字节流
 * 3. 而后经过defineClass()方法将其转换为Class对象
 */
public class SelfClassLoader extends ClassLoader {
    private String classPath;
    public SelfClassLoader(String classPath) {
        this.classPath = classPath;
    }

    /**
     * 经过 difineClass,将一个字节数组转换为Class 对象
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte(name);
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

    /**
     * 根据路径将指定的文件读取为byte 流
     * @param name
     * @return
     * @throws IOException
     */
    private byte[] loadByte(String name) throws IOException {
        name = name.replaceAll("\\.", "/");
        FileInputStream fis = new FileInputStream(classPath + "/" + name
                + ".class");
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;
    }
}

另外一个种实现自定义类加载器的方法:

/**
 * 1. 加载指定packageName下的类
 * 2. 用自定义类加载器进行加载,若是加载失败,再交给父加载器进行加载
 */
public class UrlSelfClassloader extends URLClassLoader {
    private String packageName = "";

public UrlSelfClassloader(URL[] urls, ClassLoader parent) {
    super(urls, parent);
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    Class<?> aClass = findLoadedClass(name);
    if (Objects.nonNull(aClass)){
        return aClass;
    }
    if (!packageName.startsWith(name)){
        return super.loadClass(name);
    }else {
        return findClass(name);
    }
}
}

如何使用自定义的类加载器

public static void main(String args[]) throws Exception {
    MyClassLoader classLoader = new MyClassLoader("");
    Class clazz = classLoader.loadClass("");
    Object obj = clazz.newInstance();
    Method helloMethod = clazz.getDeclaredMethod("hello", null);
    helloMethod.invoke(obj, null);
}

总结

在本文中,咱们讲解了类加载器的实现模型,分析了在 JDK 中类加载器的源码实现,并根据源码中的代码实现,自定义了一个类加载器的实现。

此外相信通过五和六两篇文章的学习,你们应该对如何将类加载入虚拟机中有了系统的理解。

后面的文章中,咱们就要进入 JVM 的内部了,从下篇文章开始,咱们就开始逐步讲解 JVM 的内存布局,了解 JVM 中的各个逻辑上划分的存储结构以及其做用,欢迎各位读者浏览。

iceWang公众号

文章在公众号「iceWang」第一手更新,有兴趣的朋友能够关注公众号,第一时间看到笔者分享的各项知识点,谢谢!笔芯!

本系列文章主要借鉴《深刻分析 Java Web 技术内幕》和《深刻理解 Java 虚拟机 - JVM 高级特性与最佳实践》。

相关文章
相关标签/搜索