Java是平台无关性语言,而构成平台无关性的基石就是字节码。其实,虚拟机并不关心class的来源是什么语言,
只要它符合class文件应有的结构就可以在java虚拟机上运行
该文件是一组以八位字节为基本单位的二进制流,中间没有任何分隔符。其中常量池是class文件结构中与其它项目关
联最多的数据类型,也是占用class文件空间最大的项目之一
常量池之中主要存放两类常量:
A, 字面量:如文本字符串,被声明为final的常量值
B, 符号引用:包括类和接口的全限定名,字段的名称和描述符,方法的名称和描述符
当虚拟机运行时需要从常量池获得对应的符号引用,再在类创建是或运行时解析并翻译
到具体的内存地址中
Java中天成可以动态扩展的语言特性就是依赖运行期间动态加载和动态链接这个特点实
现的,类在整个生命周期中包括了:加载,连接(验证,准备,解析三个阶段称为连接),
初始化,使用和卸载七个阶段
其中除了解析阶段之外,其余的阶段顺序是确定的,类的加载必须按照这种顺序按部就
的“开始”,注意不是按部就班地进行或完成,因为这些阶段通常都是相互交叉地混合式进
行的,通常会在一个阶段执行的过程中调用或者激活另外一个阶段,而解析阶段在某种情况
可以在初始化阶段之后在开始,这样做是为了支持java语言的运行时绑定,或者动态绑定
注意,虚拟机规范严格规定了有且只有四种情况必须立即对类进行初始化:
A. 使用new关键字实例化对象的时候,读取或者设置一个类的静态字段的时候
以及调用一个类的静态方法的时候(被final修饰,已在编译期间把结果放入调用类的常量池的静态字段除外,下面会有程序说明)
B. 当初始化一个类的时候,如果发现其父类没有被初始化,那么先要触发其父类的初始化(但是一个接口初始化时,并不要求父接口全部都完成初始化,只有在真正使用父接口的时候才会初始化)
C. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果没有进行过初始化,则需要触发其初始化
D. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个类
这四种场景中的行为称为对一个类进行主动引用,除此之外所有引用类的方式,都不会触发初始化,称为被动引用
注意,对于静态地段,通过子类引用父类的静态字段,不会触发子类的初始化
public class SuperClass {
static{
System.out.println("Super init");
}
public static int value = 123;
}
***************************
public class SubClass extends SuperClass{
static{
System.out.println("Sub init");
}
public static void main(String args[]){
System.out.print(SubClass.value);
}
}
对于该程序,输出只会是Superinit,因为对于静态字段,只有直接定义这个字段的类才会被初始化。
情景一:由于常量在编译阶段会存入调用类的常量池中,本质上没有引用到定义常量的类,因此也不会触发定义常量的类的初始化,看下面代码
Publicclass A{
static{
System.out.println("ConstClass init");
}
publicstaticfinal Stringstr ="Hello";
}
publicclass B {
publicstaticvoid main(String[] args){
System.out.println(A.str);
}
}
该段代码输出为Hello,虽然str定义在类A中,但是在编译阶段将此常量的值“hello”存入到调用类B的常量池中,对常量A.str的引用实际上转换为B类对自身常量池的引用了
情景二:当然,下面代码会先输出ConstClass init
Hello
Public class A{
static{
System.out.println("ConstClass init");
}
publicstaticfinal Stringstr ="Hello";
publicstaticvoid main(String[] args){
System.out.println(A.str);
}
}
A. 为什么呢?因为现在类A中含有main方法,根据前面所说的四种必须初始化情况的情况D, 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个类,也就是说在调用main方法之前就已经将A初始化了,所以会输出static语句块的语句