最近在阅读 《Inside the JVM》 这本书,结合一些日常工作学习中的感想,随便写一些东西,蜻蜓点水,不必有章法。
类的初始化工作,主要是将静态变量、常量初始化为“正确”的值(也就是程序员希望设定的特定值而非其类型的默认值),以及其它一些需要在初始化类的时候需要做的工作(如读取配置文件等)。通常我们可以这样做:
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)使用类中声明的非常量静 态字段。考虑下面的例子:
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常量),它在编译的时候已经在使用它的所有地方用这个 常量值直接替换了,也就是说,经过编译,实际上主运行类变成了这样:
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!"); } }
自然地,它打印结果的时候就无需初始化甚至无需加载相关的类了。
实际中我的项目也有很多活生生的例子。比如,很多时候我们想要用一个类来管理项目中通用的常量,避免“魔数”的代码臭味,比如:
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://www.iteye.com/topic/512550