JVM总结(四):JVM类加载机制

这一节咱们来总结一下JVM类加载机制。具体目录以下: 
java

  虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终造成能够被虚拟机直接使用的Java类型,这就是Java虚拟机的类加载机制。程序员

  在Java中,类型的加载、链接和初始化过程都在程序运行期间完成的,这种策略虽然会使类加载时增长一些性能开销,可是提供了高度的灵活性,Java里天生能够动态扩展的语言特就是依赖于运行期动态加载和动态链接的特色实现的。 
  Class文件指的是一串二进制的字节流。实际上,每一个Class文件都有可能表明着Java语言中的一个类或者接口。
数组

类加载的过程

类加载过程归纳

 
  在这七个过程当中,加载、验证、准备、初始化、卸载这5个阶段的顺序是必定的,类的加载过程必须按照这种顺序循序渐进地开始,而解析过程则不必定:它在某个状况下能够在初始化阶段以后再开始,这是为了支持Java语言语言的运行时绑定(也叫动态绑定和晚期绑定)。 
  这里强调的是:类加载阶段都是互相交叉地混合式进行的,一般是在一个阶段执行的过程当中调用、激活另外一阶段。
安全

说说引用

  对类的初始化操做可分为主动引用和被动引用 
主动引用:在如下5种状况下会进行类的主动引用的初始化操做:
数据结构

  • 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,若是类没有进行初始化,则须要先触发其初始化。生成这4条指令最多见的代码情景是:使用new关键字实例化对象、读取过设置一个类的静态字段(被final修饰、已在编译期把结果放进常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
  • 使用java.lang.reflect包对类进行反射调用时,若是类没有进行过初始化,则应须要先触发其初始化。
  • 当初始化一个类时,若是发现其父类尚未进行过初始化,会触发其父类实例化。
  • 当虚拟机启动时,用户须要指定一个要执行的主类(包含main方法类),虚拟机会先实例化那个类。
  • 当使用JDK1.7的动态语言支持时,若是一个java.lang.invoke.MethodHandle实例的最后解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,而且这个方法句柄所对应的类没有进行过初始化,则要先触发其初始化。 
    被动引用:全部引用类的方式都不会触发初始化。 
    对于静态字段,只有直接定义这个字段的类才会被初始化,所以经过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。 
    经过数组定义引用类,不会触发此类初始化:当初始化对象数组时,并不会实际触发对象的初始化操做。可是会触发一个是由虚拟机自动生成的、直接继承于java.lang.Object的子类,建立动做由字节码指令newarray触发。值得注意的是:该类表明了实际的对象数组,数组中应有的方法和属性都实如今这个类里。Java语言对数组的访问比C/C++相对安全是由于这个类分装了数组元素的访问方法。 
    常量在编译阶段会存入调用类的常量池中,本质上并无直接引用到定义常量的类,所以不会触发定义类的初始化。

值得注意的是: 
  接口也有本身的初始化过程:编译器会为接口生成“()”类构造器,用于初始化接口中所定义的成员变量。 
  接口和类初始化的区别:当一个类在初始化时,其父类都基本上初始化过了,然而接口在初始化的时候,只有真正用到父接口的时候(如引用接口中定义的常量)才会进行初始化。
性能

详解类加载全过程:

加载

在加载阶段,虚拟机须要完成如下3件事情:spa

  • 经过一个类的全限定名来获取定义此类的二进制字节流。
  • 将这个字节流所表明的静态存储结构转化为方法区的运行时数据结构。
  • 在内存中生成一个表明这个类的java.lang.Class对象,做为方法区这个类的各类数据的访问接口。

  非数组类的加载是可控性最强的。用户除了使用系统提供的引导类加载类来完成,也能够由用户自定义的类加载器去加载(重写一个类加载器的loadClass())。 
  注意:数组类自己不经过类加载器建立,它是由JVM直接建立的。但数组类和类加载器仍有很紧密的关系,由于数组类的元素类型最终是靠类加载器去建立。 
  加载完成后,虚拟机外部的二进制字节流就按照虚拟机所需格式存储在方法区之中,方法区中的数据存储格式由虚拟机实现自行定义。而后在内存中实例化一个java.lang.Class类的对象(能够在Java堆中,也能够在方法区中),该对象将做为程序去访问方法区中的这些类型数据的外部接口。 
  加载阶段与链接阶段的部份内容(如一部分字节码文件格式验证动做)是交叉进行的,加载阶段还没有结束,链接阶段就可能开始了。可是夹在加载阶段进行的动做,仍然属于链接阶段的内容。
指针

验证

  验证是链接的第一步,目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,而且不会危及虚拟机自己的安全。 
验证阶段的四个步骤:文件格式检验、元数据检验、字节码检验、符号引用检验。
对象

  • 文件格式检验: 
    检验字节流是否符合Class文件格式的规范,而且能被当前版本的虚拟机处理。检验可能包含下列几种:是否以魔数开头、主次版本号是否在虚拟机的处理范围以内,常量池中的常量是否不被支持、文件是否被删除或附加什么信息等等。 
    只有经过文件格式检验的二进制字节流才能进入内存的方法区进行存储,因此后面的3个检验阶段都是基于方法区的存储结构进行的,不会在操做字节流。
  • 元数据检验: 
    对字节码描述的信息进行语义分析,以保证其描述的内容符合Java语言规范的要求。 
    验证点包括:是否有父类(除了object)、父类是否继承了不可被继承的类(被final修饰的类)、若是这个类不是抽象类,是否实现了其父类或接口之中要求实现的全部方法、类中的方法和字段是否与父类产生矛盾(覆盖了父类的final字段、出现不合规矩的方法重载等)。 
    元数据检验主要是对类的元数据信息进行语义校验,保证不符合Java语言规范的元数据信息不存在。
  • 字节码检验: 
    经过数据流和控制流分析,肯定程序语义是合法、符合逻辑的。第二阶段是对元数据信息中的数据类型作了检验,这一阶段是对类的方法体进行校验分析,保证被校验类的方法在运行时不会作出危害虚拟机安全的事情。 
    检验点包括:保证任意时刻操做数栈的数据类型与指令代码序列都能配合工做、保证指令跳转不会跳转到方法体以外的地方、保证方法体内的类型转换都是有效的。 
    事实上,即使是通过字节码检验后的方法体也不必定是安全的。
  • 符号引用检验 
    最后一个检验发生在虚拟机将符号引用转化为直接引用时,这个转化动做将在链接的第三阶段–解析阶段中发生的。符号引用检验能够看做是对类自身之外(常量池中的各类符号引用)的信息进行匹配性校验。 
    校验点:符号引用中经过字符串描述的全限定名是否能找到对应的类、在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段、符号引用中的类、字段、方法的访问权限是否能让当前类访问到等。 
    符号引用检验的目的是确保解析动做的正常执行,若是没法经过符号引用检验,将会抛出java.lang.IncompatibleClassChangeError异常的子类,如IllegalAccessError、NoSuchfiledError、NoSuchMethodError等。

准备

  准备阶段是正式为类变量分配内存并设置类变量初始值的阶段。这些变量所使用的内存将在方法区中进行分配。此时进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一块儿分配在Java堆中。另外,在这里分配的静态类变量是将其值定义为0等默认值,而不是咱们定义的。由于这时还没有执行任何Java方法,咱们定义的赋值的putStatic指令是程序被编译后,存放在类构造器()方法中,因此正确的赋值将在初始化阶段执行。 
  若是类变量被final修饰,那么在这种状况下,在编译时Javac将会为该变量生成ConstantValue属性,在准备阶段虚拟机会根据该属性设置类变量的正确值。
继承

解析

  解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。 
a、符号引用:以一组符号来描述所引用的目标,符号能够是任何形式字面量,只要使用时无歧义地定位到目标就行。 
b、 直接引用:直接引用是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。引用的目标已经在内存中存在。 
  虚拟机实现能够根据须要来判断到底在类被加载器加载时就对常量池中的符号引用进行解析,仍是等到一个符号引用将要被使用时才去解析它。解析动做主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。

初始化

类加载的最后一步,真正执行类中定义的Java程序代码(字节码)。 初始化阶段是执行类构造器()方法的过程,根据程序员经过程序制定的主观的计划去初始化类变量和其余资源。

相关文章
相关标签/搜索