从JDK源码查看类加载过程

Java代码执行过程

执行Java代码的过程:
Java肯定是没办法对系统资源进行初始化的,所以首先肯定是由c++来初始化资源,例如创建Java虚拟机等,执行Java.exe调用JVM.dll创建Java虚拟机 -> 调用由c++编写的引导类加载器,该类加载器会加载核心类库 -> 调用sun.misc.Launcher的构造方法会创建Launcher类对象,在该构造方法中会创建扩展类加载器和应用类加载器
image
执行过程如下图所示
image
因为Java类加载模式是懒加载,所以并不会在一开始就将所用类一次性加载到JVM中,会根据双亲委派机制对类进行加载。

各种类加载器

引导类加载器: 用于加载核心类库,比如rt.jar、或String.class,引导类加载器为最上层加载器,加载的目录为\JDK\1.8\jre\lib
扩展类加载器:用于加载扩展类包,扩展类加载器在引导类加载器下层,加载目录为JDK\jre\lib\ext
应用类加载器:用于加载用户自定义的类,加载目录为target目录下的class文件。
自定义类加载器: 用户自定义类加载器,加载目录可以自定义。

类加载的过程

加载:将class文件以二进制数组读取到内存中。
验证:校验class文件的字节码是否符合JVM规则。
初始化:给静态变量分配内存并设置默认值。
解析:将符号引用转换为直接引用,该阶段会将静态符号引用转换为内存地址值用于执行,这个过程被称为静态链接,动态链接是指在程序执行过程中进行符号引用转换。
初始化:为静态变量赋值并执行静态语句块。
image

符号引用:硬盘中保存的class文件中的main class 类名之类的,一般这些信息存储在静态常量池中,使用javap命令可以看到class中的符号引用有哪些,这系符号引用会在解析阶段替换为内存地址。
静态常量池是存储在.class类文件中。在执行编译Java代码的过程中,编译器并不知道方法名、变量名等一些字段会被加载到内存的什么地方,只能先将其替换成符号,在需要执行class方法的时候才会将其加载到内存中并替换为直接引用,直接引用就是内存引用。
image

双亲委派机制

当进行类加载时,会将类先委托到最上层类进行加载,如果上层已经加载过了那么直接返回,如果没有被加载过,那么就会校验当前类加载器是否与被加载的类匹配,如果匹配则进行加载,如果不匹配则向下传递,由下层类进行加载。
image
这是JDK源码的类加载相关

    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;
        }
    }

当类进行加载时会在此检查类是否被加载过 如果被加载过则直接进行返回
image
之后会委托上层类进加载器进行加载,上层类加载器依旧会调用loadClass判断是否类是否被加载过 如果被加载过则直接进行返回。如果parent为空的话那么就表示已经到引导类加载器了,然后使用引导类加载器进行加载。
image
之后就会执行当前类加载器的加载功能,在改方法中还会判断是否为合法的加载路径。
如果不能加载成功,因为是嵌套掉的的,所以会返回下一层类加载器进行加载,这样就做到了双亲委派机制。

自定义类加载器

在AppClassLoader中有个findClass方法用于加载类,我们只要模仿这个类便可以自定义一个类加载器出来,该类的主要功能是将传入的class转为byte数组,然后调用defineClass进行加载,defineClass方法中的调用有许多本地方法,没办法查看是如何实现类加载的,调用该方法经过了以上介绍的类加载的过程。

image

该类的loadBytes将传入的文件以字节流的格式进行读取并保存到字节数组中返回。
findClass方法获取字节数组并调用defineClass进行加载。

public class LyraClassLoader extends ClassLoader {
    private String path;

    public LyraClassLoader(String path) {
        this.path = path;
    }

    public byte[] loadBytes(String name) throws IOException {
        name = name.replaceAll("\\.", "/");

        FileInputStream fileInputStream = new FileInputStream(path + "/" + name + ".class");
        int available = fileInputStream.available();

        byte[] bytes = new byte[available];
        fileInputStream.read(bytes);
        fileInputStream.close();

        return bytes;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] bytes;
        try {
            bytes = this.loadBytes(name);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return defineClass(name, bytes, 0, bytes.length);
    }
}

打破双亲委派机制

双亲委派机制是由loadClass方法实现的,我们将这个类重写一下,然后复制原来的代码 将双亲委派机制相关的代码删掉就行了
image
但是代码还是有些问题,因为打破了双亲委派机制,无法委托上层加载Object类,所以会报错。
image
解决方法是判断传入的包名是否为自定义的类,如果不是的话就用父类的类加载器进行加载,这样就可以非自定义包委托上层加载了。
image

自定义类加载器的parent默认为应用类加载器,因为

posted @   RainbowMagic  阅读(123)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示