什么情况下需要开始类加载过程的第一个阶段:加载?Java虚拟机规范中并没有进行强制约束,这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段,虚拟机规范则是严格规定了有且只有5种情况必须立即对类进行**“初始化”**(而加载、验证、准备自然需要在此之前开始): 有且仅有以下五种情况:
需要注意的是如果是数组数据,比如:
String[] strArr = new String[10]; 复制代码
这个语句会涉及到两个类:
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证大致上会完成下面四个阶段的检验工作:
准备阶段比较简单,搞清楚以下几点即可:
public static int value=123;
只是将 value 赋值为 0 而不是 123。 注:各种Java 各种基本类型 0 值对照表如下
4. 但需要注意的是, 类常量 如public static final int value=123;
则 value 在准备阶段即被赋值为 123,至于原因也是显而易见的
解析是将符号引用转化成直接引用的阶段。符号引用和直接引用在《深入理解 Java 虚拟机》中的定义如下:
上面的定义看的一脸懵逼,我举个栗子来理解上面一大段文字。
public class Test { public static final mian(String[] args) { System.out.println("Hello, /word!"); //1 } } 复制代码
如上可以看到 Test 是一个非常简单的类,Test 类中调用了 System.out.pintln 接口向屏幕上输出 Hello, world 。该类的 .class 文件中的字符串常量中(层层转换后)肯定会包含以下的一个常量:
invokevirtual java/io/PrintStream.println:(Ljava/lang/String;)V 复制代码
很明显可以看出以上是一个方法的调用(即 System.out.println())指令:其中 java/io/PrintStream 是方法所在的类,println 是方法名,Ljava/lang/String 表示参数类型,这样就能唯一确定需要调用的方法,而 java/io/PrintStream.println:(Ljava/lang/String;)V
即我们的所说的符号引用。同样如果访问其他类、接口、类的字段、类的方法、接口的方法等,都需要在 .class 文件中通过符号引用指定。由于只是一个字符串,所以是虚拟机无关的。 Test 类的.class 文件最终是要被加载到虚拟机内存中才能被执行的,而在内存中方法的访问是通过地址实现的。所以需要将符号引用 java/io/PrintStream.println:(Ljava/lang/String;)V
转换成一个指向方法在内存中具体地址的指针,.class 中指令变成类似如下形式:
invokevirtual 0xfe1886d2;(配图如下) 复制代码
其中 0xfe1886d2 即直接引用,直接引用也可以是内存地址的偏移量或者间接句柄等,只要能在内存中通过该引用访问到目标即可。通过这个例子可以简单的理解符号引用和直接引用以及它们的关系,从而明白解析阶段的作用。
类初始化阶段是类加载过程的最后一步,该阶段才真正开始执行类中定义的Java程序代码(或者说是字节码)。
类初始化过程即执行 <clinit>()方法的过程,而 <clinit>() 方法又是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的。需要注意的是:
初始化的时机在类加载加载时机一章节已经列出,是为了区分类加载时机和类初始化时机。只有清晰的理解需要初始化的各种场景才能真正把握初始化的含义。
类的加载很多人似懂非懂,特别是 解析和初始化 这里,总结为一句即: 准备和初始化 阶段完成了 static 变量的内存分配和赋值以及 static 代码块中代码的执行。出道网上的面试题,在不百度的情况下写出最后输出的字符串试试,如果你能正确写出答案那大概率已经弄到了解析和初始化阶段,面试题如下:
class Grandpa { static { System.out.println("爷爷在静态代码块"); } } class Father extends Grandpa { static { System.out.println("爸爸在静态代码块"); } public static int factor = 25; public Father() { System.out.println("我是爸爸~"); } } class Son extends Father { static { System.out.println("儿子在静态代码块"); } public Son() { System.out.println("我是儿子~"); } } public class InitializationDemo { public static void main(String[] args) { System.out.println("爸爸的岁数:" + Son.factor); //入口 } } 复制代码