Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换、解析、初始化,最终形成可以被虚拟机直接使用对Java类型,这个过程称为虚拟机的类加载机制。
一、类加载的时机
一个类从被加载到虚拟机内存中开始到卸载出内存为止,整个生命周期会经历加载、验证、准备、解析、初始化、使用、卸载七个阶段。

其中加载、验证、准备、初始化、卸载这5个阶段到顺序是确定到,但是解析阶段不确定,它可以在初始化阶段之后再开始。
《Java虚拟机规范》严格规定了有且只有以下六种情况必须立即对类进行初始化,而加载、验证、准备自然要在初始化之前进行。
1、遇到new、getstatic、putstatic、invokestatic四个字节码指令时
1)使用new关键字实例化对象
2)读取或设置一个类型对静态字段(不能被final修饰)
3)调用一个类型对静态方法
2、使用反射包对方法对类型进行反射调用时
3、初始化类对时候需要先对其父类进行初始化
4、当虚拟机启动对时候,用户需要指定一个要执行对主类,虚拟机会先初始化这个类
5、当使用JDK7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化、则需要先触发其初始化。
6、当一个接口这定义了JDK8新加入的默认方法(default修饰)如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
二、类加载过程
1、加载
虚拟机需要完成三件事
1、通过一个类的全限定名来获取定义此类的二进制字节流
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构(方法区这的数据存储格式完全由虚拟机实现自行定义)
3、在堆内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
二进制流的获取方式:
从ZIP压缩包中读取、从网络中获取、运行时计算生成(例如动态代理技术)、由其他文件生成、从数据库中读取、从加密文件中获取等等。
加载阶段与连接阶段的部分动作是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,但这些夹在加载阶段之中进行但动作,仍然属于连接阶段但一部分,这两个阶段但开始时间仍然保持这固定的先后顺序。
2、验证
目的就是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
验证阶段大致会完成下面四个阶段的验证动作:
文件格式验证
元数据验证
字节码验证
符号引用验证
验证阶段对于虚拟机的类加载机制来说,是一个非常重要、但却不是必须要执行但阶段,因为验证阶段只有通过或不通过但区别,只要通过了验证,其后就对程序运行期没有任何影响了。如果程序运行但全部代码都已经被反复使用和验证过了,在生产环境的实施阶段就可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
3、准备
该阶段是正式为类中定义的静态变量分配内存并设置变量初始值的阶段。从概念上讲,这些变量所使用的内存都应该在方法区中进行分配,方法区本身是一个逻辑上的区域,在JDK7之前,HotSpot使用永久代来实现方法区时是完全符合这种逻辑概念的。在JDK8之后,类变量就会随着Class对象一起存在堆中。
4、解析
该阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
具体内容可以参考《深入理解Java虚拟机》
5、初始化
该阶段虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。
初始化阶段会根据程序员通过程序编码制定的主观计划去初始化类变量和其他资源。(就是执行类构造器<clinit>()方法的过程)
<clinit>方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{})中的语句合并产生的,其顺序是由语句在源文件中出现的顺序决定的。
1、<clinit>方法与类的构造函数(实例构造器<init>方法)不同,它不需要显示的调用父类构造器,虚拟机会保证在子类的<clinit>方法执行前,父类的<clinit>方法已经执行完毕。
2、父类中定义的静态语句块要优先于子类的类变量赋值操作。
3、<clinit>方法对于类或接口来说不是必须的,如果一个类中没有 静态语句块,也没有对类变量的赋值操作,那么编译器可以不为这个类生成<clinit>方法。
4、接口中不能使用静态语句块,但仍然由变量初始化但赋值操作。
5、Java虚拟机必须保证一个类但<clinit>方法在多线程环境中被正确但加锁同步。