JVM原理学习笔记(三) —— 类的初始化class?A?extends?B?{ ??????public?static?int?intVal??30 ??????pub
JVM原理学习笔记(三) —— 类的初始化
class?A?extends?B?{ ??????public?static?int?intVal?=?30; ??????public?static?String?strVal; ?????? ??????static?{ ??????????strVal?=?readConfig("ItemA"); ??????} ????????private?static?String?readConfig(String?key)?{ ??????????.... ??????} ??}??
class A extends B { public static int intVal = 30; public static String strVal; static { strVal = readConfig("ItemA"); } private static String readConfig(String key) { .... }}??? 当一个类被加载,它将顺序经历四个过程:验证、准备、解析、初始化(一个类被加载以前,如果其父类尚未初始化,那么JVM会先去用以上四个过程对这个父类进行初始化)。验证只是检查class文件是否符合java语义并且不会损害JVM的完整性,这里不多赘述。准备阶段是为类成员分配内存,同时根据该成员的类型赋给它相应的默认值,对于上面的示例类A,经过准备阶段后状态是这样的:
intVal = 0;
strVal = null;
??? 解析是把符号引用转为直接引用的过程,比如,原来JVM只知道类A有一个成员叫"intVal",通过解析则知道了该成员的地址是0xBBEDF,以后再使用这个成员的时候,就直接去这个内存地址去找了。同时在这个阶段,类的方法比如上面的readConfig()也会被映射到某个内存地址以待调用。
??? 初始化则是利用类定义的JAVA代码确定成员变量的初值(如果对某个成员没有相应的java代码对其进行初始赋值操作,那么它就沿用在准备阶段获得的该类型默认值)。在这个阶段,所有的静态代码都会被执行,事实上,类在编译时编译器是会把这些静态代码封装到一个<clinit>方法中去的,在使用这个类的时候,初始化过程就会调用这个<clinit>方法。这个方法程序员是不能调用的,它只能被JVM调用。
??? 以上对JVM初始化一个类的过程做了一些讲解,但是JVM究竟什么时候才会初始化一个类呢?总的来说,JVM会在“主动”使用一个类的时候将该类初始化。所谓“主动”,大致有6种已知的行为,对我们比较常见的是:1)试图创建该类的一个新实例;2)调用该类声明的一个静态方法;3)使用类中声明的非常量静态字段。考虑下面的例子:
Java代码

public?interface?Angry?{ ??????String?greeting?=?"Grrrr!"; ??????int?angerLevel?=?Dog.getAngerLevel(); ??} ????public?class?Dog?implements?Angry?{ ??????public?static?final?String?greeting?=?"Wong,?Wong,?Wong!"; ?????? ??????static?{ ??????????System.out.println("Dog?was?initialized."); ??????} ????????public?static?int?getAngerLevel()?{ ??????????System.out.println("Angry?was?initialized."); ??????????return?1; ??????} ??} ????public?class?Main?{ ??????public?static?void?main(String[]?args)?throws?Exception?{ ??????????testClassInit(); ??????} ????????public?static?void?testClassInit()?throws?Exception?{ ????????????//passive?use?of?Angry ??????????System.out.println(Angry.greeting); ????????????//passive?use?of?Dog ??????????System.out.println(Dog.greeting); ??????} ??}??
public interface Angry { String greeting = "Grrrr!"; int angerLevel = Dog.getAngerLevel();}public class Dog implements Angry { public static final String greeting = "Wong, Wong, Wong!"; static { System.out.println("Dog was initialized."); } public static int getAngerLevel() { System.out.println("Angry was initialized."); return 1; }}public class Main { public static void main(String[] args) throws Exception { testClassInit(); } public static void testClassInit() throws Exception { //passive use of Angry System.out.println(Angry.greeting); //passive use of Dog System.out.println(Dog.greeting); }}??? 如果可以的话,看官可以先猜一猜输出的结果是什么。
??? 其实注释里已经提示得很清楚了,两个输出语句都是用被动方式调用的Angry和Dog的成员,因此无论是"Dog was initialized."还是"Angry was initialized."都不会被打印。换句话说,Angry和Dog都没有被初始化。
??? 为什么类没有被初始化但它的静态final成员还是可以正确打印呢?实际上,像声明为“public static final String”的静态常量(注意,在接口里声明String效果是一样的,它实际上也是个final常量),它在编译的时候已经在使用它的所有地方用这个常量值直接替换了,也就是说,经过编译,实际上主运行类变成了这样:
Java代码

public?class?Main?{ ??????public?static?void?main(String[]?args)?throws?Exception?{ ??????????testClassInit(); ??????} ????????public?static?void?testClassInit()?throws?Exception?{ ????????????//passive?use?of?Angry ??????????System.out.println("Grrrr!"); ????????????//passive?use?of?Dog ??????????System.out.println("Wong,?Wong,?Wong!"); ??????} ??}??
public class Main { public static void main(String[] args) throws Exception { testClassInit(); } public static void testClassInit() throws Exception { //passive use of Angry System.out.println("Grrrr!"); //passive use of Dog System.out.println("Wong, Wong, Wong!"); }}自然地,它打印结果的时候就无需初始化甚至无需加载相关的类了。
??? 实际中我的项目也有很多活生生的例子。比如,很多时候我们想要用一个类来管理项目中通用的常量,避免“魔数”的代码臭味,比如:
Java代码

class?ConstManager?{ ??????public?static?final?String?SIGN_DASH?=?"-"; ??????public?static?final?String?SIGN_SLASH?=?"/"; ??????public?static?final?String?SIGN_COMMA?=?","; ??????...... ??}??
class ConstManager { public static final String SIGN_DASH = "-"; public static final String SIGN_SLASH = "/"; public static final String SIGN_COMMA = ","; ......}然后我们会在项目的某处会以ConstManager.SIGN_COMMA 来代替逗号。这对我们管理项目是有益的,当需要修改一个符号的时候,只需要在一处修改再编译就行了。但实际上,编译后的class文件,在用到这些常量的代码块的位置,这些符号早就被替换成真实的","之类的值了。在项目运行的过程中,ConstManager甚至从来都没有机会被应用服务器加载。http://dingchaoqun12.blog.163.com/blog/static/11606250420104220219334/