美文网首页
JAVA类加载机制

JAVA类加载机制

作者: IT小师弟 | 来源:发表于2018-09-07 18:00 被阅读5次

    1.JAVA类加载机制

    JAVA类加载机制:虚拟机把Class文件加载到内存中,并对数据进行校验,转换解析和初始化,最终转换为可以被虚拟机直接使用的JAVA类型。

    类加载的时机:类从被加载到内存中开始,到类被卸载,生命周期包括:加载,验证,准备,解析,初始化,使用,卸载七个阶段。其中,验证,准备,解析统称为连接阶段。

    加载,验证,准备,使用,卸载这五个阶段必须顺序开始,但解析阶段在某些情况下可以在初始化的后面,为了支持JAVA语言的动态绑定。

    虚拟机规定了,有且只有四种情况会对类直接进行初始化

    1.碰到new,getstatic,putstatic,invokestatic四条字节码指令时,主要是new对象时,和对类变量进行设置和获取,以及对类方法进行调用。

    2.reflect反射调用类方法使时要对类进行初始化。

    3.当初始化一个类时,发现其父类还未初始化时,立刻初始化其父类。

    4。当虚拟机启动时,用户需要指定一个主类,main函数入口类,会初始化。

    有且只有以上四种情况属于主动引用,其余属于被动引用。

    类加载的过程:

    加载:1.通过类的全限定名来获取此类的二进制字节流。

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

               3.在JAVA堆中产生一个class对象,作为访问方法区数据结构的入口。

    验证:验证阶段是整个连接阶段的一部分,这一阶段的目的是确保所加载Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致有4个检验过程:文件格式验证,元数据验证,字节码验证和符号引用验证。

    1.文件格式验证:验证字节流是否符合Class文件格式规范。主要有一下几点:

    是否以魔数0xCAFEBABE开头

    主,次版本号是否在虚拟机处理范围内

    常量池中常量是否有不被支持的常量类型

    指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量

    不符合编码的数据

    ......

    该验证仅为格式验证,保证输入的字节流能够正确解析并存放于方法区内。

    2.元数据验证:对字节码描述的信息进行语义分析,以保证其描述信息符合JAVA语言规范。验证点如下:

    这个类是否有父类,除了Object外,所有类都应当有父类。

    这个类的父类是否继承了不被允许继承的类,被final修饰的类。

    如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法。

    类中的字段,方法是否与父类产生了冲突。

    ......

    第二极端的主要目的是对类的元数据信息进行语义校验,保证不存在不符合Java语言规范的元数据信息。

    3.字节码验证

    第三阶段是整个验证过程中最复杂的一个阶段,主要工作是进行数据流和控制流分析。

    保证类的方法在运行时不会做出危害虚拟机安全的行为:

    保证任何时候操作数栈的数据类型与指令代码序列都能配合工作,不会出现如在操作栈中放置了一个int类型的数据,使用时却按照long型载入本地变量表中

    保证跳转指令不会跳转到方法体以外的字节码指令上

    保证方法体中的类型转换是有效的。比如将一个子类对象赋值给父类这是安全的,但是把父类对象赋值给子类数据类型,甚至毫无关系的一个数据类型,就是危险的不合法的。

    4.符号引用验证

    最后一个阶段的校验发生在虚拟机将符号引用转换为直接引用的时候,这个转化动作将在连接的第三个阶段-解析阶段中发生。可以看做是对类自身以外的信息进行匹配性的校验,通常验证一下内容。

    符号引用中通过字符串描述的全限定名能否找到对应的类。

    在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和实现。

    符号引用中的类,字段和方法的访问性,能否被当前类所访问。

    准备:准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。这个进行内存分配的仅仅只是类变量,static修饰,并不包括实例变量,而且所设置的初始值是零值而非定义时所赋的值。

                       public static int value =123;

    在准备阶段过后,value的值为0,并不是123,123在初始化阶段才会被赋值。

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

              符号引用:符号引用以一组符号来描述所引用的目标,符号可以使任何形式的字面量,只要使用时能无歧义地定位到目标即可,符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。

             直接引用:直接引用可以是直接指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用。那引用的目标必定已经在内存中存在。

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

                 在准备阶段,变量已经赋过一次系统要求的初始值,但在初始化阶段,则是根据程序员通过程序指定的主观计划去初始化类变量和其他资源。初始化阶段是执行类构造器<clinit>()方法的过程。

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

              <clinit>()方法与累的构造函数不同,它不需要显式的调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的该方法已经执行完毕,第一个执行该方法的肯定是Object类。

             由于父类的先执行,所以父类中定义的静态代码块要优先于子类的变量赋值操作。

            <clinit>()方法并不是必须的,如果一个类中没有静态代码块和变量赋值,编译器就可以不为该类生成该方法。

            接口中没有静态代码块,但是有变量初始化的赋值操作,但是和类不同的是,接口在执行该方法时并不需要先执行父类的,只有父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始话时亦不会执行接口的<clinit>()方法。

            虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁和同步,如果多个线程同时去初始化一个类,只有一个线程会执行<clinit>()方法,其他线程会阻塞等待。

    相关文章

      网友评论

          本文标题:JAVA类加载机制

          本文链接:https://www.haomeiwen.com/subject/qucgwftx.html