Class文件加载详解

Class文件加载过程

java从编码到运行

​ 首先我们来看一下Java是如何从编码到执行的呢? 我们有一个x.java文件通过执行javac命令可以变成x.class文件,当我们调用Java命令的时候class文件会被装载到内存中,这个过程叫做classloader。一般情况下我们自己写代码的时候会用到Java的类库,所以在加载的时候也会把Java类库相关的类也加载到内存中。装载完成之后会调用字节码解释器和JIT即时编译器来进行解释和编译,编译完之后由执行引擎开始执行,执行引擎下面对应的就是操作系统硬件了。下图是大体的流程:

img

class文件的加载过程

接下来主要讲的是一个class文件是怎么从硬盘上到内存中,并开始执行的。

类加载主要有三个过程:loading 、linking 、initializing;其中linking又分为三个步骤:verification 、preparation 、resolution;

img

1、首先Loading是什么意思呢?是把一个class问价load到内存中去;

2、接下来是Linking分为了三小步:

  • verification 是用来校验加载进来的class文件是否符合class文件标准,如果不符合直接就会被拒绝了;
  • preparation 是将class文件静态变量赋默认值而不是初始值,例如static int i =8;这个步骤并不是将i赋值为8,而是赋值为默认值0;
  • resolution 是把class文件常量池中用到的符号引用转换成直接内存地址,可以访问到的内容;

3、initializing 成为初始化,静态变量在这个时候才会被赋值为初始值;

下面为类加载过程的简化图:

img

类加载器的加载过程是分成不同的层次来加载的,不同的类加载器来加载不同的class文件, Bootstrap >Extension>Application>Custom(自定义类加载器)

1、第一个类加载器的层次为:Bootstrap 称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库。

2、第二个类加载器的层次为:Extension 是用来加载扩展类的,主要负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目录下的所有jar包。

3、第三个类加载器的层次为:Application 又称为系统类加载器,负责在JVM启动时,加载来自在命令java中的classpath或者java.class.path系统属性或者CLASSPATH操作系统属性所指定的JAR类包和类路径。

4、第三个类加载器的层次为:CustomClassLoader(自定义加载器)

特别注意一点这个的层级关系并没有继承的关系在里面,只是单单纯纯的语法上的继承;

下图为类加载的一个全过程:

​ 用比较通俗的话来解释这个过程,当有一个类需要被加载时,首先要判断这个类是否已经被加载到内存,判断加载与否的过程是有顺序的,如果有自己定义的类加载器,会先到custom class loader 的cache(缓存)中去找是否已经加载,若已加载直接返回结果,否则到App的cache中查找,如果已经存在直接返回,如果不存在,到Extension中查找,存在直接返回,不存在继续向父加载器中寻找直到Bootstrap顶层,如果依然没找到,那就是没有加载器加载过这个类,需要委派对应的加载器来加载,先看看这个类是否在自己的加载范围内,如果是直接加载返回结果,若不是继续向下委派,以此类推直到最下级,如果最终也没能加载,就会直接抛异常 ClassNotFoundException,这就是双亲委派模式。

理解双亲委派模式:

1、父加载器:不是类加载器的加载器,也不是类加载器的父类加载器(此处意思是没有父类与子类之间的继承关系)。

package com.example.demo.classloader;

/**
 * 验证了父加载器不是加载器的加载器
 */
public class ParentAndChild {
    public static void main(String[] args) {
        //AppClassLoader
        ClassLoader classLoader = ParentAndChild.class.getClassLoader();
        System.out.println(classLoader);

        //null  这里AppClassLoader的加载器不是ExtClassLoader  而是Bootstrap
        ClassLoader appclassLoader = ParentAndChild.class.getClassLoader().getClass().getClassLoader();
        System.out.println(appclassLoader);

        //ExtClassLoader   AppClassLoader的父加载器是ExtClassLoader
        ClassLoader parent = ParentAndChild.class.getClassLoader().getParent();
        System.out.println(parent);

        //null
        ClassLoader parentparent = ParentAndChild.class.getClassLoader().getParent().getParent();
        System.out.println(parentparent);

        //null
        ClassLoader parentparentparent = ParentAndChild.class.getClassLoader().getParent().getParent().getParent();
        System.out.println(parentparent);

        /**输出结果*/
        //sun.misc.Launcher$AppClassLoader@18b4aac2
        //null
        //sun.misc.Launcher$ExtClassLoader@23fc625e
        //null
        //Exception in thread "main" java.lang.NullPointerException at com.example.demo.classloader.ParentAndChild.main(ParentAndChild.java:22)
    }
}

2、双亲委派:其工作原理的是,如果一个类加载器收到了类加载请求,并不会直接去加载,而是自下而上的向顶层类加载器查找是否已经被加载了,如果被加载就不用进行加载,如果未被加载过,则会自上而下的检查是否属于自己加载的范围,如果属于则加载,如果不属于则向下委托,直到类被加载进来才能叫做成功,如果加载不成功就会抛异常classnotfoundexeption,这就叫做双亲委派。

3、为什么要搞双亲委派模式?

主要是为了安全,这里可以使用反证法,如果任何类加载器都可以把class加载到内存中,我们就可以自定义类加载器来加载Java.lang.string。在打包时可以把密码存储为String对象,偷偷摸摸的把密码发送到自己的邮箱,这样会造成安全问题。

posted on 2021-08-16 23:03  six、hc  阅读(443)  评论(0编辑  收藏  举报

导航