Java的类加载过程与双亲委派模型

类加载

Java的类加载过程中涉及到双亲委派模型,而提到双亲委派模型又免不了讨论类的加载过程,既然这样的话,干脆就把这两者放一块吧。

目录如下,可根据自己需要自行食用。


Java的类加载过程可以分为三个步骤:加载、连接、初始化。其中连接过程又可以分为验证、准备、解析三个步骤。在这里我画了个图更加方便展示:
在这里插入图片描述其中加载、验证、准备和初始化发生的顺序是确定的,解析过程则不确定。这是因为Java支持动态绑定(多态),在程序运行的时候才能知道最终对象的引用,所以解析阶段可能发生在初始化阶段之后。这几个阶段的开始时间是按照图中的顺序开始的,但他们的运行时间有长有短,因此有可能先开始执行的阶段,后面的阶段执行完了,他还在执行。

接下来就围绕加载、连接、初始化这三个部分来看一下整个的类加载过程。

加载阶段

类加载的第一个阶段,在此阶段,JVM虚拟机会进行下面的三个步骤:

1、通过一个类的全限名来获取其定义的二进制字节流。

2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

3、在Java堆中生成一个代表这个类的java.lang.Class对象,通过这个对象,可以实现对方法区中数据的访问。

加载阶段可以使用JDK提供的类加载器(这个类加载器在下面会介绍到),也可以使用自定义的加载器,加载阶段完成之后,.calss文件已经被加载到方法区中并在堆区中创建了对应的Class对象。

这个Class文件可能会来源于以下这四个地方:

1.本地编译好的class文件(也就是.java文件经过javac编译成的class文件)。

2.jar包中的class文件。

3.动态代理生成的class文件。

4.压缩文件中的jar,class,war文件。

连接阶段

我们在上面可以看到连接阶段其实是可以划分为三个小阶段的,所以在这里我们也就从这三个小阶段来进行分析。

验证阶段

此阶段主要是为了保证要加载的类的正确性,确保加载的类信息符合JVM规范,没有安全方面的问题。一般会从下面四个方面来进行验证。

1、文件格式验证:验证字节流是否符合Class文件格式的规范。

2、元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。

3、字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。

4、符号引用验证:确保解析动作能正确执行。

验证阶段是对于类的加载非常重要,但他并不是必须的,它对程序运行期没有影响。当然了,你也可以使用-Xverifynone参数来关闭大部分的类验证措施,以此来缩短虚拟机类加载的时间。

准备阶段

此阶段会为类的静态变量分配内存,并将其初始化为默认值,这个默认值不是代码中赋予的初始值,初始值的赋予则是在java类初始化阶段完成。

啥意思呢?比如我们定义int a=10。这个阶段会为a赋默认值为0,至于a=10的步骤,是在初始化阶段完成的。同理,像boolean类型的变量,会默认赋值为false。

但是像静态变量:static int b=20,这个阶段是会直接将b赋值为20。

解析阶段

这个阶段将字符引用解析为直接引用。

将类中的符号引用转化为直接引用,虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行,在此之前的符号引用仅仅是一个标识。

初始化阶段

初始化,为类的静态变量赋予正确的初始值(上个阶段已经赋值(默认值)),JVM负责对类进行初始化,主要对类变量进行初始化。

JVM初始化一个类包含如下几个步骤:

1、假如这个类还没有被加载和连接,则程序先加载并连接该类。

2、假如该类的直接父类还没有被初始化,则先初始化其直接父类。

3、假如类中有初始化语句,则系统依次执行这些初始化语句。

所以JVM总是最先初始化java.lang.Object类。

类初始化的时机(对类进行主动引用时):

1.创建类的实例 ,new 生成对象。

2.访问类的静态变量,或静态变量的赋值。

3.调用类的静态方法。

4.使用反射获取类的信息,强制创建类的实例对象。

5.初始化某个类的子类时,会初始化父类。

6.直接使用java.exe运行某个类的主类,main方法,会初始化。

PS:如果访问某个类的final变量,则不进行初始化。

小结

至此,类的加载过程已经执行完毕,总体来看,其实细节还是蛮多的,但是其实我们需要了解的是个大致的流程,主体脉络没问题,细节什么的其实不必全部care,我在这里用个流程图简单表示一下:
在这里插入图片描述

类加载器与双亲委派机制

类加载器

我们在上面的就知道加载器是作用于加载阶段的。JVM的类加载器可以分为两种:原始类加载器(primordial class loader)和类加载器对象(class loaderobjects)。原始类加载器是Java虚拟机实现的一部分,类加载器对象(Java编写)是运行中的程序的一部分。

类加载器利用命名空间进行区分,子加载器的命名空间包含所有父加载器的命名空间。因此通过子加载器加载的类能看见父加载器加载的类,由父加载器加载的类不能看见子加载器加载的类。如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见。

类加载器有三种:Bootstrap ClassLoader、Extension ClassLoader、System ClassLoader。除此之外,我们也可以自定义类加载器。他们之间的关系可以用一张图来表示:
在这里插入图片描述

启动类加载器:Bootstrap ClassLoader

主要负责加载存放在\jre\lib目录下,或被-Xbootclasspath参数指定的路径中的(sun.boot.class.path),通常指的是rt.jar里的类。相对于另外的加载器,启动类加载器比较特殊,HotSpot虚拟机中它利用C++ 实现,作为虚拟机自身的一部分,它的实现设计JVM底层的实现细节,开发者无法直接获取它的引用,所以从他的子类获取它返回的为null,生成Java虚拟机的同时就会生成启动类加载器。Bootstrap ClassLoader之后自动加载Extension ClassLoader,,将其父Loader设为Bootstrap Loader(获取时为null),之后Java实现的类加载器才能够工作。

扩展类加载器:Extension ClassLoader

该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载\jre\lib\ext目录中的类库。Bootstrap Loader自动加载AppClass Loader(系统类加载器),并将其父Loader设为Extended Loader。

应用程序类加载器:Application ClassLoader

该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,作为应用程序中类默认的类加载器。

双亲委派机制

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

Java程序中区分一个类通过其全类名进行匹配,如果一个类的全类名相同就被认为是一个类,但是在JVM中识别一个类则是利用全类名+类加载器,“全类名+类加载器”被作为一个唯一的标识识别不同的类,不同的类加载器会把类放在不同的命名空间中。

我们可以看下双亲委派机制的源代码,说的其实就比较清楚啦:

protectedClass<?> loadClass(Stringname,booleanresolve)throws ClassNotFoundException
   {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);//首先检查class是否已被加载过
            if (c == null){//如果没有被加载过,循环查找父类加载器
                longt0 = System.nanoTime();
                try {
//父类加载器不为空,且不为bootstrap加载器,执行父类的loadClass(),
//把类加载请求一直向上抛,直到父加载器为null(是Bootstrap ClassLoader)为止
                    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.
//父加载器开始尝试加载.class文件,加载成功就返回一个java.lang.Class,
//加载不成功就抛出一个ClassNotFoundException,给子加载器去加载
                    longt1 = System.nanoTime();
                    c = findClass(name);
                    // this is the defining class loader; record the stat
           sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);           sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
//解析这个.class文件,主要就是将符号引用替换为直接引用的过程
                resolveClass(c);
            }
            returnc;
        }
   }

采取双亲委派机制,只要是放在特定目录的类,无论哪个类开始加载他,最终都只能是能够加载它的那个类加载器加载本类,此时用户可以定义和这个被加载的类相同全类名的类,放在ClassPath目录下,则最终加载之后他们是放在不同的命名空间中的,如果不采取双亲委派,此时某个类被加载时就会被第一次调用它的加载器加载,这时在定义不同目录下相同全类名的类,就会导致混乱。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值