浏览 5736 次
锁定老帖子 主题:JVM原理学习笔记(三) —— 类的初始化
精华帖 (1) :: 良好帖 (2) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2009-11-08
类的初始化工作,主要是将静态变量、常量初始化为“正确”的值(也就是程序员希望设定的特定值而非其类型的默认值),以及其它一些需要在初始化类的时候需要做的工作(如读取配置文件等)。通常我们可以这样做: 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甚至从来都没有机会被应用服务器加载。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-11-09
支持一下,加深了对jvm的理解..
|
|
返回顶楼 | |
发表时间:2009-11-09
讲的很不错,谢谢分享。
|
|
返回顶楼 | |
发表时间:2009-11-09
LZ,请问《Inside the JVM》在哪买的?我上次找遍了都没找到。
|
|
返回顶楼 | |
发表时间:2009-11-09
treblesoftware 写道 LZ,请问《Inside the JVM》在哪买的?我上次找遍了都没找到。
绝版了,买二手书,或者下电子书。 |
|
返回顶楼 | |
发表时间:2009-11-09
不错!学习了
《Inside the JVM》怎么不出新版本呀 |
|
返回顶楼 | |
发表时间:2009-11-10
期待楼主更多的学习笔记!
|
|
返回顶楼 | |
发表时间:2009-11-10
在接口中声明的变量,默认是static final的,学习了
|
|
返回顶楼 | |
发表时间:2009-11-10
finux 写道 不错!学习了
《Inside the JVM》怎么不出新版本呀 我也希望出新版啊,JDK1.5/6 改变还是很大的,绝对有必要再出一版。 |
|
返回顶楼 | |