1.概述:
JVM把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化的,最终形成可以被JVM直接使用的Java类型。Class文件由类装载器装载后,在JVM中形成一个份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息,如:构造器,属性,方法等,java允许用户借由这个元信息对象间接调用Class对象的功能
2.周期
加载→验证→准备→解析→初始化→使用→卸载
加载 验证 准备 初始化 发生顺序固定 ,解析不一定,可能会在初始化后,为的是支持运行时绑定
是顺序开始,但是不是顺序进行或完成,几个阶段可能会交叉进行
绑定:方法调用与方法所在类关联
静态绑定:编译器绑定,final static private 构造方法
动态绑定:运行时绑定
3.加载
加载主要完成三件事
通过类的全限定名,获取其定义的二级制流(二进制流并不只是单纯的从Class文件中获取,也可以从jar包,网路等地方获取)
将字节流代表的静态存储结构转为为方法区运行时数据结构
在java堆中生成代表这个类的Class对象,作为方法区中这些数据的访问入口类的加载器
java类的唯一性由类加载器和类本身共同决定,也就是说,同一个class文件,类加载器不同,则这两个类不相同,表现上是equals(),isInstance()等方法的返回结果不同,也包括instanceOf方法的判定结果
从虚拟机角度,类加载器的分类
启动加载器:使用C++实现,是虚拟机自身的一部分
其它类加载器: 由java语言实现,独立于虚拟机之外,全部继承自java.lang.ClassLoader
从开发角度,类加载器的分类
启动加载器: Bootstrap ClassLoader 加载条件=jdk\jre\lib或者-Xbootclasspath指定的目录 && 能被虚拟机识别的类库(如rt.jar、java.*开头的类). 启动加载器不能被java程序直接引用
扩展类加载器:Extension ClassLoader 由ExtClassLoader实现,加载条件= jdk\jre\lib\ext目录 || java.ext.dirs指定的目录 。开发者可直接使用扩展类加载器
应用程序类加载器:Application ClassLoader 由AppClassLoader实现,负责加载classPath目录下的类,可直接使用,是程序默认的类加载器,没有可定义。
双亲委派模型
自定义类加载器→应用程序类加载器→扩展类加载器→启动类加载器
工作流程:先交由上层找,找不到再自己找
优点:使java类随着它的类加载器一起具备了带有优先级的层次关系,例如Object类在rt.rar中,最终总会由启动加载器加载,就保证系统中只有一中Object类
4.验证
目的 :保证Class字节流中的信息符合当前虚拟机的要求,而且不会危害虚拟机的安全。
四个阶段:
文件格式验证:保证字节流符合Class文件格式规范,确保输入的字节流能正确的解析并存储到方法区,结果改阶段,字节流才会进入内存的方法区存储
元数据验证:语义校验,符合java’语法规范
字节码验证:数据流和控制流分析,保证运行时无危害当前虚拟机的行为
符号引用验证:发生在符号引用转为直接引用时,主要对类意外的信息进行匹配性校验
5.准备
正式为类变量(static变量)分配内存并设置初始值
仅包含类变量
初始值为类型的默认值,而不是代码中指定的默认值
例如 int staic a = 3 ; 在该阶段的初始值是0而不是3
注意的小点:
static或全局变量,不显示指定是默认值为类型默认值,局部变量必须显示赋值,否则编译不通过
static和final同时修饰的变量必须声明时赋值;只被final修饰的可以声明时赋值也可以初始化时赋值
引用类型 reference不显示指定时默认null
数组不显示指定时默认都为其类型默认值
static和final同时修饰,会在准备阶段就会被初始化为ConstValue属性所指定的值,例如public static final a = 3;该阶段a就是3了
6.解析
虚拟机将常量翅中的符号引用转化为直接引用的过程
解析动作主要针对:类/接口(CONSTANT_Class_info)、字段(CONSTANT_Fieldref_info)、类方法(CONSTANT_Methoddref_info)、接口方法(CONSTANT_InterfaceMethoddref_info)
类或接口解析:根据直接引用的目标是数组类型还是普通对象类型而进行不同的解析
字段解析:现在本类中找,找到就结束,如果没找到,则按照接口继承关系从上往下找,找到则结束,如果还没找到,则在按照类的继承关系从上往下找。
类方法解析:与字段类似,只是搜索是先搜索父类再搜索接口
接口方法解析:与类方法类似,只是接口没有父类,只有父接口而已
7.初始化
才真正执行类中定义的java程序代码,准备阶段对类变量进行过类型默认值的复制,初始化阶段则是按照主观进行真正赋值。该阶段还可以理解为是执行类构造器clinit()方法的过程
clinit()的执行规则:
clinit()方法是由编译器自动收集类变量的赋值动作和静态语句中的语句合并而成。收集顺序就是代码顺序,这样,静态语句块只能访问在其之前的类变量,而在其之后的类变量只可以赋值但是不能访问
clinit()方法,在子类执行前,虚拟机会确保其父类已经执行完毕,而不是显示的调用父类的clinit这与init方法是不同的
如果一个类或接口没有静态语句或者类变量,编译器可以不为其生成clinit方法
接口的clinit与类的clinit不同的是,执行接口的clinit不需要限执行其父接口的clinit,只有接口中定义的类变量被使用是才会执行clinit方法
类的clinit方法是线程安全的
感谢兰亭风雨
http://blog.youkuaiyun.com/ns_code/article/details/17881581