0

    java类机制加载制度深度解析(JVM基础篇)

    2023.07.13 | admin | 128次围观

    引言

    目前工作上大部分都是springboot项目,启动的时候都只需要简单地运行main方法就可以进行启动了。不过当我们用java -jar命令运行启动类的main方法进行启动时,对应的类加载机制以及运行过程是怎么样的呢? (●ˇ∀ˇ●) 容我娓娓道来

    java类的加载运行过程

    当我们用java命令运行main方法时,类加载器会将主类优先加载至JVM中找不到或无法加载主类,在主类的运行过程中,类加载器会根据主类的需要,逐步的将使用到的类加载至JVM中。

    注意:java中的类并不是在jar包启动时全部加载,而是在主类的运行过程中,使用到才会进行加载,很重要

    java类的加载到执行过程大概分为以下几步:

    加载:通过io流的方式读取硬盘上的class文件,并且将mian方法以及main方法中使用到的类进行加载,并验证class文件中的字节码是否正确准备:给类的静态变量分配内存,并赋予默认值(看到这个就知道静态变量的加载优先级了O(∩_∩)O),例如int类型默认值为0找不到或无法加载主类,对象的默认值为null等解析:该阶段会将静态方法名替换为指向数据存储的指针,即静态链接过程初始化:对类的静态变量初始化为指定的值,并执行静态代码块

    类的整体加载流程如下:

    加载并验证--->准备---> 解析 --->初始化---> 使用----> 卸载

    java类加载器以及其双亲委派机制启动类加载器(使用的是C语言写的,java中不可见):负责加载jvm运行时需要的核心包,比如javaws.jar、rt.jar,java.lang.String、Long这些java类都是此加载器进行加载扩展类加载器(ExtClassLoader):负责加载加载jvm运行的扩展包,主要是我们jre/ext文件夹下面的包,这些包应该是java的开发人员当年不敢改动核心包(开发扩展包和核心包的开发人员估计是两个部门的吧吧哈哈哈),又必须加扩展功能时,扩展出来的包应用类加载器(AppClassLoader):这个就主要是负责加载我们写的那些业务代码啦,加载的是项目classpath目录下的class文件自定义加载器:当以上的三种加载器满足不了我们的要求的时候(比如加载的class文件不在classspath目录下),就需要自定义类加载器来加载了

    //加载器示例        
    ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
    ClassLoader extClassloader = appClassLoader.getParent();
    ClassLoader bootstrapLoader = extClassloader.getParent();
    System.out.println("the bootstrapLoader : " + bootstrapLoader);
    System.out.println("the extClassloader : " + extClassloader);
    System.out.println("the appClassLoader : " + appClassLoader);

    双亲委派机制效果图

    java类加载是存在一种亲子结构的,专业属于双亲委派机制,即类加载器需要加载某个类时,会不断的委托给自己的父类进行类加载工作,直到找到此工作委托给最顶级的类(不存在父类),此时才会开始正式加载类,当顶级类加载不了对应的类后,会不断的委托给自己的子类进行加载,直到加载到目标类为止。语言比较干涩,通过AppClassLoader的源码(省略了和机制无关的代码)来解释一下就清楚了:

    //ClassLoader的loadClass方法,里面实现了双亲委派机制
    protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 检查当前类加载器是否已经加载了该类
            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) {}
                if (c == null) {
                    //这里可以看到,如果根加载器和父加载器都无法加载目标类时,就会在当前classpath下寻找目标类
                    c = findClass(name);
                }
            }
            if (resolve) {  //不会执行
                resolveClass(c);
            }
            return c;
        }
    }

    保证沙箱安全:java的核心类库不会被修改,保证了核心api功能的正常避免类重复加载:父加载器加载过的内容,子加载器无需加载,保证了类的唯一性自定义类加载器

    如果需要自定义类加载器需要继承Classloader类,可以重写findClass(可以自定义从指定的路径读取类文件)、loadClass(可以定义类加载方式,Classloader中的loadclass方法遵循的是双亲委派机制,如果需要打破此机制,需要重写这个方法)

    下图中我自定义了loadclass方法,成功加载了非本地classpath下的class文件

    自定义classloader

    使用自定义classloader加载class

    现在我们开始打破双亲委派机制,即使当前项目的classpath存在此class,还是加载指定目录下的class文件,如下图:

    自定义loadclass方法

    当前classpath下User.class内容

    打破双亲委派机制效果图

    版权声明

    本文仅代表作者观点。
    本文系作者授权发表,未经许可,不得转载。

    发表评论