类加载机制

在看这篇类加载机制以前建议你们有一些关于class文件结构的知识,省得后面有些概念很差理解。
在阅读这篇以前能够先看一下:class文件的结构
如需转载请注明出处,类的加载机制java

首先咱们来了解一下类的生命周期:数组

图1 类的生命周期

其中加载、验证、准备、初始化和卸载这5个阶段顺序是肯定的,必须按顺序执行。 解析阶段不必定按照顺序,它在某些状况下能够在初始化阶段以后再开始,这是为了支持Java语言的运行时绑定。
那么一个类什么时候加载?
答案是:不必定。
对于类什么时候加载Java虚拟机规范没有强制约束,能够由具体实现来决定。
可是Java虚拟机严格规范了类 什么时候初始化
类初始初始化条件: 当且仅当如下5种状况时必须对类进行初始化。
这个当且仅当,确实能够说是一个严格的规范了。

一个类什么时候初始化?

1,遇到new、getstatic、putstatic或invokestatic、这4条字节码指令时,若是类没有被初始化,则须要先触发其初始化。

(1)new:使用new关键字实例化对象的时候。
(2)getstatic或putstatic:读取或设置一个类的静态字段(不包括被final关键字修饰的变量,被final修饰、已在编译期把结果放入常量池的静态字段字段除外)。
(3)invokestatic:调用一个类的静态方法。安全

2,使用java.lang.reflect包的方法对类进行进行反射调用时。

3,子类初始化时。(若是一个类的在初始化时父类尚未初始化,则先初始化父类)

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

5,当使用JDK1.7的动态语言支持时,若是一个java.lang.invoke.MethodHandler实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄时。

下面介绍几种类不初始化的状况,被动引用。数据结构

被动引用

1,经过子类引用父类的静态字段,不会致使子类初始化。

图2 被动引用例一

2,经过数组定义来引用类,不会触发此类的初始化。

图3 被动引用例二

3,常量在编译阶段会存入调用类的常量池中,本质上并无直接引用到定义常量的类,所以不会触发定义常量的类初始化。

引用其余类的final static变量后会把被引用类的值在编译器写入引用这个值的类的常量池,在编译后两个类就没有任何关系了,因此引用final static变量不会致使类的初始化。多线程

图4 被动引用例三

注意:接口与类不一样,接口在初始化时,不要求其父类所有完成初始化,只有在真正使用到父类接口的时候(如引用了接口中定义的常量)才会初始化。
下面介绍类的加载过程,类的生命周期包括:加载、验证、准备、解析、初始化、使用 、卸载。函数

类的加载过程

1,加载

这里多说一句,类加载(Class Loading)和加载(Loading)是两个概念,但愿你们不要混淆。另外Class对象对于咱们经常使用的HotSpot虚拟机是放在方法区中,而咱们平时new的实例对象是在堆上分配空间。post

类加载阶段,虚拟机要完成如下3件事。编码

(1)经过类的全限定名获取定义此类的二进制流。

(2)将流的静态结构转化为方法区的运行时数据结构。

(3)在内存中生成一个表明这个类的java.lang.Class对象。(对于HotSpot虚拟机,Class对象是存放在方法区里的。)

2,验证

验证:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身安全。spa

验证阶段大体完成下列4个动做。

1)文件格式验证:对检查格式、版本。

(1)是否以0xCAFEBABE开头。(检查魔数)
(2)主、次版本号是否在当前虚拟机处理范围以内。(JVM对于编译的class文件能够向下兼容,可是不能向上兼容,因此这里须要检查当前这个class文件是否在当前版本JVM处理范围以内。)
(3)常量池的常量中是否有不被支持的常量类型。(检查常量tag标志)
(4)指向常量的各类因此中是否有指向不存在的常量或不符合类型的常量。
(5)CONSTANT_Utf8_info型常量是否有不符合UTF8编码的数据。
(6)Class文件中各个部分及文件自己是否有被删除的或者附加的其余信息。
(7)等等……线程

以上验证完成后,字节流才会进入内存的方法区中进行存储。

2)元数据验证:对字节码描述进行语义分析。

(1)这个类是否有父类。(除了Object都应该有父类)
(2)这个类的父类是否继承了不容许被继承的类。(被标记为final的类)
(3)若是这个类不是抽象类,是否实现了父类或接口中要求实现的方法。
(4)类中字段、方法是否与父类产生矛盾。(例如覆盖了父类的final字段不符合规则的方法重载。)
(5) 等等……

3)字节码验证

(1)保证任意时刻操做数栈的数据类型与指令代码序列都能配合工做,例如不 会出现int类型被按照long类型加载。
(2)保证指令不会跳转到方法体之外的字节码指令上。
(3)保证方法体中类型转换是有效的,例如不能把父类型转换成子类型。
(4)等等……

4)符号引用验证

(1)符号引用中经过字符串描述的全限定名是否能找到对应的类。
(2)在指定类中是否存在符合方法的字段描述以及简单名称所描述的方法和字 段。
(3)符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问。
(4)等等……

3,准备

准备阶段:

1, 正式为类变量分配内存,内存在方法区中进行分配。

2, 设置类变量的初始值。

首先注意两个方面:

1, 类变量是指static修饰的变量,而不是实例变量,实例变量将会在对象实例化时随着对象一块儿分配在Java堆中。

2, 这里说的初始值一般状况下是数据类型的零值,假设: public static int value = 123; 初始值是0,而不是123,赋值123的动做会在初始化阶段才会执行。(图5是java类型的零值表)

图5 java类型零值表

既然是一般状况,确定也有特殊状况,若是:

public static finalint value = 123;	  
复制代码

这种状况,变量有static final修饰的时候,在准备阶段虚拟机就会将value设置为123。

4,解析

解析:将常量池(class文件的常量池)内的符号引用替换为直接引用。

下表是符号引用和直接引用的区别:

引用类型 存在形式 引用目标位置
符号引用 任何形式字面量,无歧义定位便可 不必定已经加载到内存中
直接引用 直接指向目标的指针、相对偏移量或者间接定位到目标的句柄。 若是有了直接引用,那引用的目标一定已经存在在内存中。

5,初始化

初始化阶段才真正开始执行类中定义的Java程序代码(或者说字节码)。

初始化过程是执行类构造器<clinit>()方法的过程。

下面介绍<clinit>():

1,<clinit>()方法自动收集类中全部类变量(static)的赋值动做和静态语句块中的语句合并产生的,编译器收集顺序由代码出现顺序决定。

2,<clinit>()方法与类构造函数(实例构造器<init>()方法)不一样,虚拟机会保证父类的<clinit>()执行完毕后再执行子类的<clinit>()。

所以虚拟机中第一个被执行的<clinit>()方法的类是?

答案是:Object类。

3,先看一段代码,输出结果为?

图6 <clinit>()方法执行顺序


因为父类<clinit>()方法先执行,因此父类静态代码块优先于子类赋值语句执行,答案应该输出2。

4,<clinit>()对于类或接口不是必需的,若是一个类没有静态语句块,也没有对静态变量赋值操做,则编译器能够不为这个类建立<clinit>()方法。

5,接口中不能有静态块,可是能够有静态变量,因此也有<clinit>()方法,但与类不一样,只有当父接口的变量被调用时才会执行父类的<clinit>()方法。一样,接口的实现类在初始化时也不会执行接口的<clinit>()方法。

6,虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁、同步,若是多线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其余线程都须要阻塞。(同一个类加载器下,一个类型只会初始化一次。)

以上就是有关类加载的一些内容,欢迎若有问题欢迎留言,或发邮件Rick.Hsu@hotmail.com。

相关文章
相关标签/搜索