什么时候初始化一个类
仅有5种情况
- 遇到new, getstatic, putstatic, invokestatic指令时,如果该类尚未初始化,则进行初始化。注意:读取static final的数字或字符串不算,这些值都已经在编译的时候放入当前类的常量池,不需要去原来的类读取。
- 反射
- 初始化一个类的时候,如果父类尚未初始化,则先初始化父类。
- 命令行中制定的main类
- jdk7中跟invokedynamic相关
类加载器
- 启动类加载器(bootstrap classloader):加载JAVA_HOME/lib下的标准类,如rt.jar
- 扩展类加载器(extension classloader):加载JAVA_HOME/lib/ext下的类,或者变量java.ext.dirs目录下的类
- 应用程序类加载器(application classloader):即getSystemClassLoader()取到的加载器,又叫系统加载器。负责加载classpath上的类,即用户自己的类。
类加载器的双亲委派模型
http://www.ibm.com/developerworks/cn/java/j-lo-classloader/
两个类只有全名相同,且使用了同一个类加载器加载,才被认为是同一个类。如果两个类名字相同,但是类加载器不同,则不可以互相赋值。这里所说的加载是指ClassLoader.defineClass()函数。
自己定义的类加载器应当遵循双亲委派模型,即去实现findClass(在实现findClass的时候调用defineClass方法)和loadClassData方法,这样会首先委派给父加载器,当父加载器失败的时候才会调用自定义类的findClass方法。如果直接覆盖loadClass方法,就绕过了双亲委派模型。另外Thread类的setContextClassLoader方法的存在也是为了打破双亲委派模型。
编译器的类型
- 前端编译器:将java文件编译成class文件。如jdk的javac,Eclpise JDT的ECJ。
- JIT编译器:如hotspot vm中的C1,C2编译器。
- AOT编译器:直接将java文件编译成本地代码。
javac编译器
本身就是用java实现的,代码位于SRC/langtools/src/share/classes/com/sun/tools/javac目录下。编译期几乎不会做任何优化。
词法分析:com.sun.tools.javac.parser.Scanner。语法分析:com.sun.tools.javac.parser.Parser;语法树:com.sun.tools.javac.tree.JCTree。填充符号表:com.sun.tools.javac.comp.Enter。语义分析,包括标注检查和数据及控制流分析两个步骤;标注检查:com.sun.tools.javac.comp.Attr和com.sun.tools.javac.comp.Check,检查变量是否已经声明、数据类型、及进行常量折叠等;数据及控制流分析:com.sun.tools.javac.comp.Flow,检查变量使用前是否已赋值、方法的每条路径都有返回值、异常是否已被处理等。解语法糖:com.sun.tools.javac.comp.TransTypes,com.sun.tools.javac.comp.Lower。字节码生成:com.sun.tools.javac.jvm.Gen,com.sun.tools.javac.jvm.ClassWriter。