在看这篇类加载机制以前建议你们有一些关于class文件结构的知识,省得后面有些概念很差理解。
在阅读这篇以前能够先看一下:class文件的结构
如需转载请注明出处,类的加载机制。java
首先咱们来了解一下类的生命周期:数组
(1)new:使用new关键字实例化对象的时候。
(2)getstatic或putstatic:读取或设置一个类的静态字段(不包括被final关键字修饰的变量,被final修饰、已在编译期把结果放入常量池的静态字段字段除外)。
(3)invokestatic:调用一个类的静态方法。安全
下面介绍几种类不初始化的状况,被动引用。数据结构
引用其余类的final static变量后会把被引用类的值在编译器写入引用这个值的类的常量池,在编译后两个类就没有任何关系了,因此引用final static变量不会致使类的初始化。多线程
注意:接口与类不一样,接口在初始化时,不要求其父类所有完成初始化,只有在真正使用到父类接口的时候(如引用了接口中定义的常量)才会初始化。
下面介绍类的加载过程,类的生命周期包括:加载、验证、准备、解析、初始化、使用 、卸载。函数
这里多说一句,类加载(Class Loading)和加载(Loading)是两个概念,但愿你们不要混淆。另外Class对象对于咱们经常使用的HotSpot虚拟机是放在方法区中,而咱们平时new的实例对象是在堆上分配空间。post
类加载阶段,虚拟机要完成如下3件事。编码
验证:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身安全。spa
(1)是否以0xCAFEBABE开头。(检查魔数)
(2)主、次版本号是否在当前虚拟机处理范围以内。(JVM对于编译的class文件能够向下兼容,可是不能向上兼容,因此这里须要检查当前这个class文件是否在当前版本JVM处理范围以内。)
(3)常量池的常量中是否有不被支持的常量类型。(检查常量tag标志)
(4)指向常量的各类因此中是否有指向不存在的常量或不符合类型的常量。
(5)CONSTANT_Utf8_info型常量是否有不符合UTF8编码的数据。
(6)Class文件中各个部分及文件自己是否有被删除的或者附加的其余信息。
(7)等等……线程
以上验证完成后,字节流才会进入内存的方法区中进行存储。
(1)这个类是否有父类。(除了Object都应该有父类)
(2)这个类的父类是否继承了不容许被继承的类。(被标记为final的类)
(3)若是这个类不是抽象类,是否实现了父类或接口中要求实现的方法。
(4)类中字段、方法是否与父类产生矛盾。(例如覆盖了父类的final字段不符合规则的方法重载。)
(5) 等等……
(1)保证任意时刻操做数栈的数据类型与指令代码序列都能配合工做,例如不 会出现int类型被按照long类型加载。
(2)保证指令不会跳转到方法体之外的字节码指令上。
(3)保证方法体中类型转换是有效的,例如不能把父类型转换成子类型。
(4)等等……
(1)符号引用中经过字符串描述的全限定名是否能找到对应的类。
(2)在指定类中是否存在符合方法的字段描述以及简单名称所描述的方法和字 段。
(3)符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问。
(4)等等……
准备阶段:
1, 正式为类变量分配内存,内存在方法区中进行分配。
2, 设置类变量的初始值。
首先注意两个方面:
1, 类变量是指static修饰的变量,而不是实例变量,实例变量将会在对象实例化时随着对象一块儿分配在Java堆中。
2, 这里说的初始值一般状况下是数据类型的零值,假设: public static int value = 123; 初始值是0,而不是123,赋值123的动做会在初始化阶段才会执行。(图5是java类型的零值表)
public static finalint value = 123;
复制代码
这种状况,变量有static final修饰的时候,在准备阶段虚拟机就会将value设置为123。
解析:将常量池(class文件的常量池)内的符号引用替换为直接引用。
下表是符号引用和直接引用的区别:
引用类型 | 存在形式 | 引用目标位置 |
---|---|---|
符号引用 | 任何形式字面量,无歧义定位便可 | 不必定已经加载到内存中 |
直接引用 | 直接指向目标的指针、相对偏移量或者间接定位到目标的句柄。 | 若是有了直接引用,那引用的目标一定已经存在在内存中。 |
初始化阶段才真正开始执行类中定义的Java程序代码(或者说字节码)。
初始化过程是执行类构造器<clinit>()方法的过程。
下面介绍<clinit>():
1,<clinit>()方法自动收集类中全部类变量(static)的赋值动做和静态语句块中的语句合并产生的,编译器收集顺序由代码出现顺序决定。
2,<clinit>()方法与类构造函数(实例构造器<init>()方法)不一样,虚拟机会保证父类的<clinit>()执行完毕后再执行子类的<clinit>()。
所以虚拟机中第一个被执行的<clinit>()方法的类是?
答案是:Object类。
3,先看一段代码,输出结果为?
4,<clinit>()对于类或接口不是必需的,若是一个类没有静态语句块,也没有对静态变量赋值操做,则编译器能够不为这个类建立<clinit>()方法。
5,接口中不能有静态块,可是能够有静态变量,因此也有<clinit>()方法,但与类不一样,只有当父接口的变量被调用时才会执行父类的<clinit>()方法。一样,接口的实现类在初始化时也不会执行接口的<clinit>()方法。
6,虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁、同步,若是多线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其余线程都须要阻塞。(同一个类加载器下,一个类型只会初始化一次。)
以上就是有关类加载的一些内容,欢迎若有问题欢迎留言,或发邮件Rick.Hsu@hotmail.com。