垃圾收集器与内存分配策略( 五 )


准备
准备阶段是正式为类中定义的变量(即静态变量,被修饰的变量)分配内存并设置类变量初始值的阶段,从概念上讲,这些变量所使用的内存都应当在方法区中进行分配,但必须注意到方法区本身是一个逻辑上的区域,在JDK 7及之前,使用永久代来实现方法区时,实现是完全符合这种逻辑概念的;而在JDK 8及之后,类变量则会随着Class对象一起存放在Java堆中,这时候“类变量在方法区”就完全是一种对逻辑概念的表述了 。
关于准备阶段,还有两个容易产生混淆的概念笔者需要着重强调,首先是这时候进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中 。其次是这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:
int value = http://www.kingceram.com/post/123;
那变量value在准备阶段过后的初始值为0而不是123,因为这时尚未开始执行任何Java方法,而把value赋值为123的指令是程序被编译后,存放于类构造器()方法之中,所以把value赋值为123的动作要到类的初始化阶段才会被执行 。
解析
解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程,符号引用在第6章讲解Class文件格式的时候已经出现过多次,在Class文件中它以、fo、nfo等类型的常量出现,那解析阶段中所说的直接引用与符号引用又有什么关联呢?
?·符号引用( ):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可 。
?·直接引用( ):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄 。直接引用是和虚拟机实现的内存布局直接相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同 。如果有了直接引用,那引用的目标必定已经在虚拟机的内存中存在 。
《Java虚拟机规范》之中并未规定解析阶段发生的具体时间,只要求了在执行、、、、、、、-、、、ldc、ldc_w、、、new、和这17个用于操作符号引用的字节码指令之前,先对它们所使用的符号引用进行解析 。所以虚拟机实现可以根据需要来自行判断,到底是在类被加载器加载时就对常量池中的符号引用进行解析,还是等到一个符号引用将要被使用前才去解析它 。
类和接口的解析
字段解析
类的方法解析
接口的方法解析
初始化
类的初始化阶段是类加载过程的最后一个步骤,之前介绍的几个类加载的动作里,除了在加载阶段用户应用程序可以通过自定义类加载器的方式局部参与外,其余动作都完全由Java虚拟机来主导控制 。直到初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序 。
进行准备阶段时,变量已经赋过一次系统要求的初始零值(类变量),而在初始化阶段,则会根据程序员通过程序编码制定的主观计划去初始化类变量和其他资源 。我们也可以从另外一种更直接的形式来表达:初始化阶段就是执行类构造器()方法的过程 。
()并不是程序员在Java代码中直接编写的方法,它是Javac编译器的自动生成物,但我们非常有必要了解这个方法具体是如何产生的,以及()方法执行过程中各种可能会影响程序运行行为的细节,这部分比起其他类加载过程更贴近于普通的程序开发人员的实际工作 。
·()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块({}块)中的语句合并产生的,()方法与类的构造函数(即在虚拟机视角中的实例构造器()方法)不同,它不需要显式地调用父类构造器,Java虚拟机会保证在子类的()方法执行前,父类的()方法已经执行完毕 。因此在Java虚拟机中第一个被执行的()方法的类型肯定是java.lang. 。由于父类的()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作 。