注意: 装载 和 加载 的区别:java
jvm把class文件加载到内存中,并对数据进行校验、解析和初始化,最终造成JVM能够直接使用的java类的全过程。安全
class只有使用的时候才会被装载,java虚拟机也不会无条件的装载class类型数据结构
加载类处于类装载的第一个阶段,将class文件的字节码加载到内存中,并将静态数据转换成方法区中的运行时数据结构,在堆中生成一个表明这个类的java.lang.Class对象,做为方法区类数据的访问入口。jvm
该过程须要ClassLoader参与。布局
加载类,JVM必须完成:测试
将java类的二进制代码合并到JVM的运行状态中。这一步包含三个操做:优化
注意:线程
java并不支持boolean类型,对于boolean类型,内部实现是Int,因为int的默认值是0,故对应的boolean默认值是false。code
此处进行内存分配的只是类变量(static修饰的变量),不包括实例变量(实例变量会在对象实例化时随着对象一块儿分配在java堆中)对象
解析,该阶段的任务就是将类、接口、字段和方法的符号引用转为直接引用
符号应用,就是一些字面量的引用,和虚拟机的内部数据结构和内存布局无关。
初始化是类装载的最后一个阶段。若是前面的操做没有问题,表示类能够顺利装载到系统中。此时,类才会开始执行java字节码。
该阶段的重要工做,是执行类的初始化方法<clinit>, 为类变量赋予正确的值。方法<clinit>是由编译器自动生成的,它是由类静态成员的赋值语句以及static语句块合并产生的。
在加载一个类以前,虚拟机老是会试图加载该类的父类,所以父类的<clinit>方法老是在子类<clinit>以前被调用。
注意:
JVM规定:一个类或者接口在初次使用时,必须进行初始化。这里的“使用”,指的是”主动使用”,包括如下几种状况:
除了以上状况属于主动使用外,其余均属于被动使用,被动使用不会引发类的初始化。
public class Parent { static { System.out.println("Parent init."); } } public class Child extends Parent { static { System.out.println("Child init."); } } public class InitMain { public static void main(String[] args) { Child child = new Child(); } }
上述示例声明了三个类:Parent、Child(extends Parent)、InitMain。若Parent被初始化,static被执行将打印“Parent Init”;若Child被初始化,将会打印“Parent init”、"Child init"。执行InitMain,打印结果为:
Parent init. Child init.
根据上述示例可知,系统首先加载Parent类,接着装载Child类。符合主动装载中的两个条件,使用new关键字建立类的实例会装载相关的类,以及在初始化子类时,必选先初始化父类。
public class Parent { static { System.out.println("Parent init"); } public static int v = 100; } public class Child extends Parent { static { System.out.println("Child init."); } } public class InitMain { public static void main(String[] args) { System.out.println(Child.v); } }
说明:Parent类中定义了类变量v,在InitMain测试类中,使用子类Child调用父类中的类变量v。
执行结果:
Parent init 100
在InitMain测试类中,经过子类Child直接访问了Parent类中的static变量v,可是子类Child并未初始化,只有父类Parent完成初始化。因此,在引用一个字段时,只有直接定义该字段的类,才会被初始化。
注意:虽然子类Child没有被初始化,可是此时Child类已经被系统加载,只是没有进入到初始化阶段。
public class FinalFieldClass { public static final String CONST_STR = "hello world"; static { System.out.println("FinalFieldClass init"); } } public class FinalFieldTest { public static void main(String[] args) { System.out.println(FinalFieldClass.CONST_STR); } }
运行结果: hello world.
分析:FinalFiledClass类没有由于其常量字段CONST_STR被引用而初始化,这是由于在Class文件生成时,final常量因为其不变性,作了适当的优化。
编译后的FinalFieldClass.class中,并无引用FinalFieldClass类,而是将其final常量直接存放在常量池中,所以FinalFiledClass类天然不会被加载。javac在编译时,将常量直接植入目标类,再也不使用被引用类。
注意:并非在代码中出现的类,就必定会被加载或者初始化,若是不符合主动使用的条件,类就不会被初始化。