当程序需要主动使用某个类,而该类还未加载到内存中,则系统会通过三个步骤来对该类进行初始化。
加载(Load):将类的class文件读入内存中,并将这些静态数据转换为方法区的动态数据结构,并为其在堆区创建一个独一的代表这个类的java.lang.Class对象,该Class对象作为方法区中类数据的访问入口。该对象不可手动创建,仅能通过系统获取。
链接(Link):将类的二进制代码合并到Java虚拟机(JVM)的运行状态中的过程,此过程分为以下三部分
验证:确保类的加载符合JVM的规范,没有安全方面的问题。
准备:正式为类变量(static修饰)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中完成分配。
解析:此过程将JVM常量池中的符号引用(常量名)替换为直接引用(地址)此时就已经将类中的常量加载到常量池中,因此在仅调用该类的常量时不会发生类的初始化!
初始化(Initialize):在JVM中执行类构造器 <clinit>() 方法的过程。
ⅰ 类构造器<clinit>() 方法是由在编译期自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器)
ⅱ 当初始化一个类的时候,若其父类还未初始化,则先触发父类的初始化,再进行子类的初始化。
ⅲ JVM会保证一个类的 类构造器<clinit>() 方法在多线程运行条件下能被正确的加锁和同步。
当类触发主动引用时,一定会发生该类的初始化。
触发场景有:new 一个该类的实例化对象、调用该类的静态变量(除了final变量)和静态方法、使用 java.lang.reflect 包进行反射调用。
当类触发被动引用时,不会发生该类的初始化。
当访问一个静态域时,只有真正声明的这个类会被初始化。
例如:通过子类访问父类的静态变量,不会导致子类被初始化、通过数组的方式定义类引用,不会触发该类的初始化、引用常量不会触发该类的初始化(常量在链接阶段就已存入JVM的常量池中)
类加载器的作用就是将类(class)装载入内存中。JVM规范定义了三类型的类加载器,分别是:
引导类加载器【使用C++编写】是JVM自带的类加载器,负责Java核心平台库(rt.jar包),用于装载核心类库,编译器无法直接获取。
扩展类加载器 主要负责加载jre/lib/ext目录下的一些扩展的jar。
系统类加载器 主要负责加载应用程序的主函数类(main),是最常用的加载器。
类加载器利用双亲委派机制来保证所加载的类文件的安全性,图解如下: