java虚拟机05-虚拟机加载类机制&类加载器

1.概述

    虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。下面我们将详细的讨论一下类的加载过程

    与那些编译时需要进行链接的语言不同,在java语言中,类的加载、链接和初始化都是在程序运行期间完成的,这种策略虽然会令类加载时稍稍增加一些性能开销,但是会为java程序提供高度的灵活性。

2.类加载的机制

    2.1步骤

        类的加载过程主要分为 7 个阶段: 1.加载(Loading) 2.验证 (verify) 3.准备 (preparation)4.解析 (resolution)5.初始化(initlainitialaztion)6.使用(using)7.卸载(unload)

    其中,加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这个过程执行。 而解析阶段则不一定:它在某些情况下可以在初始化之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定)

   2.2 什么时候开始加载?

        对于什么时候开始加载类,Java虚拟机规范中并没有进行强制约束,这点可以交给虚拟机实现来只有把握。但是对于初始化阶段,虚拟规范则严格规定了只有以下5种情况必须立即对类进行初始化(当然,加载、验证、准备必须在这之前完成)

        1.遇到 new 、getatstic 、putstatic、或invokestatic这四条指令字节码时,如果类没有进行过初始化,则要先出发其初始化

          一般生成这四条字节码指令最常见的java代码场景是:使用new 实例化对象时、读取或设置一个静态字段时(final修饰的放在常量池的不算)、调用一个类的静态方法的时候。

        2.使用 Java.lang.Reflect包的方法对类进行反射调用的时候,如果类没有发生初始化则要先进行初始化

        3.当初始化一个类的时候发现其父类没有进行初始化,则需要先触发其父类的初始化。

        4.当虚拟机启动时,用户需要指定一个要执行的主类(即含有main方法的类),虚拟机会先初始化这个类

        5.当使用动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic、REF_putstatic、REF_invokestatic方法的句柄,并且这个对应的类没有被初始化,则要进行初始化。

3.类加载过程

    3.1 加载

          加载 是类加载的第一个阶段,在类加载阶段,虚拟机需要完成三件事情:

            1.通过一个类的全限定名来获取此类的二进制字节流

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

            3.在内存中生成一个代表此类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

    3.2 验证

          验证时链接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并不会危害虚拟机自身的安全。验证阶段大概会完成4个检验

            1.文件格式验证:是否以模数 0xCAFEBABE 开头(参考字节码文件格式)、主、次版本号是否在当前虚拟机处理范围之内、常量池中是否有不被支持的常量等

            2.元数据验证:第二阶段是对字节码描述的信息进行语义校验,保证不存在不符合java语言规范的元数据信息。例如这个类是否有父类(除Object之外所有类都有父类)、是否继承了不允许被继承的类等

            3.字节码验证:这是整个验证过程中最复杂的一个阶段、主要目的是通过shuju数据流和控制流分析,确定程序语义是合法的,符合逻辑的。

            4.符号引用验证:最后一个阶段验证发生在虚拟机将符号引用转换为直接引用的过程,这个转化动作将在连接的第三阶段——解析阶段发生。符号引用验证可以看作是类对自身以外的信息进行匹配式效验。

          对于虚拟机的加载机制来说,验证阶段是一个非常重要的,但是不一定必要的阶段。如果所运行的全部代码都已经被反复使用并校验过,那么就可以不进行校验。

    3.3 准备

          准备阶段是正式为变量分配内存和初始化值的阶段,这些变量所使用的内存都将在方法区中进行分配。这个阶段有两个概念要明确一下:首先,这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,

          实例变量将会在对象实例化时随着对象一起分配在堆中。其次,这里所说的初始值通常情况下是数据类型的0值。(还没有进行明确赋值操作)。当然也有特殊情况 如 static final 值就会被直接初始化

       3.4解析

          解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

            符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。各种虚拟机的内存布局可以不同,但是他们能接受的符号引用必须是一致的。

            直接引用:可以是指向目标的指针、相对偏移量或一个能间接定位的句柄。如果有了直接引用,那引用的目标必定在内存中已经存在。

            几种解析(后续可能会写到,也可以先自行百度)1.类或接口的解析 2.字段解析 3.类方法解析 4.接口方法解析 

    3.5 初始化

          初始化是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序keyi可以通过自定义类加载器参与外,其余动作完全由虚拟机主导和控制。到了初始化阶段才真正开始执行类中定义的java代码。

          在准备阶段,变量已经赋过一次系统要求的初始值。而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化变量和其他资源

          <clinit>方法:<clinit>方法是由编译器自动收集类中的所有变量的赋值动作和静态语句块(static块)中的语句并进行合并产生的,编译器的收集顺序是语句在源文件中出现的顺序所决定的。

            静态语句块只能访问到定义在静态语句块之前的变量。定义在他之后的变量,在前面的静态语句块可以赋值,但是不能访问。

            <clinit>方法与类的构造函数不同,他不需要显示的调用父类构造器,虚拟机会保证在子类的<clinit>执行之前父类的<clinit>方法执行完毕。具体信息请自行百度。

4.类加载器

    4.1定义

        虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制流”的这个动作放到java虚拟机外部去实现,以便让应用程序自己去决定如何去获取所需要的类,这个动作的代码模块被称作为“类加载器”。

    4.2 常见的类加载器及作用:

          4.2.1 引导类加载器(启动类加载器)

                Bootstrap ClassLoader :用来加载java的核心库(rt.jar和其他核心代码库),该加载器不继承ClassLoader 是由c++实现

                             同时也用来加载拓展类加载器和应用类加载器并制定他们的父类加载器。

          4.2.2 拓展类加载器 

                Extension ClassLoader: 用来加载java拓展库,java虚拟机实现会提供一个拓展目录,该加载器会加载拓展目录下的全部类(JAVA_HOME/lib/ext)

                             拓展类加载器由sun.misc.Launch$ExtensionClassLoader实现。

          4.2.3 应用类加载器

                Application ClassLoader:他根据java应用的类路径加载类,一般来说java应用的类都是由他来加载的(classpath)

                             应用类加载器由sun.misc.Launch$AppClassLoader 实现。

          4.2.4 自定义类加载器

                自定义类加载器是开发人员通过继承java.lang.ClassLoader类的方式实现的自己的类加载器,用来满足一些特殊的需求。

                注意:只有被同一类加载器加载的同一个类才认为是相同的类;被两个加载器加载的类,即使是同一个类,jvm也不认为是同一个类。

猜你喜欢

转载自www.cnblogs.com/xiaobai1202/p/10840621.html