理解JVM之类加载机制

  类完整的生命周期包括加载,验证,准备,解析,初始化,使用,卸载,七个阶段.其中验证,准备,解析统称为连接,类的卸载在前面的关于垃圾回收的博文中已经介绍.

  加载,验证,准备,初始化,卸载这五个阶段的顺序是确定的,类的加载必须按照这种顺序按部就班的来,而解析阶段不一定,它可以在初始化阶段之后开始,这是为了支持java的运行时动态绑定.值得注意的是,上述的五个阶段只是按部就班的"开始",并不是按部就班的"进行"或者"完成",因为这些阶段通常是互相交叉混合进行的,通常会在一个阶段执行的过程调用,激活另一个阶段.

  在java虚拟机规范中并没有进行强制约束什么时候加载类,这是交给虚拟机具体实现自由把握,但是对于初始化阶段,虚拟机规范有严格规定:

  1.使用new实例化对象或者读取,设置一个类的静态字段(被final或者已在编译器吧结果放入常量池的静态字段除外)的时候以及调用一个类的静态方法的时候.  

  2.使用反射调用的时候,如果类没初始化,需要进行初始化  

  3,当初始化一个类的时候,父类没有初始化,则需要先初始化父类

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

  5.使用动态语言支持时,如果一个java.lang.MethodHandle实例最后解析结果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,并且这个句柄锁对应的类没有初始化,则初始化这个类.

  下面具体介绍类加载的过程.

1.加载

  加载阶段,虚拟机需要完成3件事情:

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

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

  3) 在内存中生成带包这个类的Class对象,作为方法区这个类的各种数据访问入口.

  对于数组类而言,情况不同,数组类本身不通过类加载器创建,它是由java虚拟机直接创建.数组类的创建过程遵循以下规则:

  1) 如果数组的组件类型(指数组去掉一个维度的类型)是引用类,那就采取上述的方法来加载这个组件类型,数组C将在加载该组件类型的类加载器的类名空间上被标识

  2) 如果数组的组件类型不是引用类型(例如int[]),虚拟机会将数组标记为与引导类加载器关联

  3) 数组类的可见性与他的组件类型的可见性一直,如果组件类型不是引用类型,那数组类的可见性将默认为public.

  加载完成后,外部二进制字节流就按照虚拟机需要的格式存储在方法区中,然后实例化一个Class类的对象作为程序访问方法区中的这些类型数据的外部接口.

2.验证

  验证是连接的第一步,这一阶段为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全.验证分为四个阶段:文件格式验证,元数据验证,字节码验证,符号引用验证

  1) 文件格式验证是验证字节流是否符合Class文件格式的规范,并且能够被当前版本的处理机处理.

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

  3) 字节码验证是只能怪过验证过程最复杂的阶段,主要是通过数流和控制流分析,确定程序语义是合法的,符合逻辑的.这个阶段对类的方法体进行校验分析,保证被校验类的方法在运行时不会出现危害虚拟机安全的事件.

  4) 符号引用验证发生在虚拟机将符号引用转化为直接引用的时候.这个动作是在解析阶段发生.

3.准备

  这个阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量锁使用的内存都在方法区中分配.值得注意的是这里进行分配内存的仅包括类变量(被static修饰的变量),不包括实例变量,其次这里设置初始值是将值设为零值,例如一个整型类变量设置的零值是0,而不是代码中写出来的值.将代码写出来的值赋值给这个变量是初始化阶段做的事情

4.解析

  解析阶段是虚拟机将常量池的符号引用替换为直接引用的过程.解析包括类或接口的解析,字段解析,类方法解析,接口方法解析

5.初始化

  初始化是类加载的最后一步,在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者换一种说法:初始化阶段是执行类构造器<clinit>()方法的过程.

  <cliinit>()方法时由编译器自动收集所有类变量的赋值动作和静态语句快中的语句合并产生的,收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块只能访问到定义域在静态语句块之前的变量,后面的变量只能赋值,不能访问.

  <cliinit>()方法还有许多规定,请自行查找相关资料

6.类与类加载器

  类加载器虽然只用于实现类加载动作,但是在java程序中的还有其他作用.对于任意一个类加载器,都有一个独立的类名空间,而被这个类加载器加载出来的类,其类名会被标记上类加载器的类名空间,则两个特性决定了一个类在虚拟机中的唯一性.也就是说我们要比较两个类是否相等,前提是要这两个类是同一个类加载器加载出来的.否则就算是同一个class文件加载出来的这两个的也是不相等的.因为instanceof这个关键字会将两个类判定为不是同一个类,

7.双亲委派模型

  这个模型的工作过程是:如果一个类加载器收到了类加载请求,它首先不会自己尝试加载这个类,而是吧这个请求委派给父类加载器去完成.因此所有的加载请求最终都会送到顶层的启动类加载器,只有父类反馈无法完成类加载让子加载器才会尝试加载.这个模型使java的类不会因为类加载器不同而产生不同的相同类.

原文地址:https://www.cnblogs.com/ouhaitao/p/8583657.html