JVM之运行时数据区
前言
从开始阅读《Java虚拟机规范》到现在已经历时二个月了,中途出现过多次想要放弃的念头,它不像源码那样直观,是什么就是什么,纯属一堆描述...前半部分都是一字不落的看完,后半部分挑选重点的内容来阅读,不然真要吐了...细节部分就留着需要的时候在来攻克它。在阅读过程中对比《深入Java虚拟机》中的知识点,后者涵盖的内容更多更丰富,翻到前序可以知道这是周志明老师集成了多本书的精华加上自己的理解形成的,之所以去了解规范是为了论证别人介绍的知识点/观点是否正确合理(终于可以开始总结了,都要哭了)。
运行时数据区
Java虚拟机在程序的执行过程中定义了运行时数据区域,这些区域有各自的用途,有的区域随着虚拟机的启动而创建,有的是随着线程的启动而建立,并在线程退出后进行销毁,其中包括程序计数器(Program Counter Register)、虚拟机栈(Virtual Machine Stack)、堆(Heap)、方法区(Method Area)、本地方法栈(Native Method Stack),如下图所示:

图中区域块的大小并不是说明有多大,单纯是为了对齐而已...
程序计数器
CPU通过给每个线程分配时间片来实现多线程的调度,也就是说在任何一个确定的时刻,一个CPU都只会执行一条线程中的指令,而为了保证下一个线程在切换的前后能够正确的执行,必须要求每个线程都有一个独立的程序计数器,各个计数器之间互不影响,独立存储,这块内存区域称之为线程私有的内存,在程序中很多地方都需要它,比如跳转、异常处理、循环、分支。如果当前线程正在执行的是一个Java方法,那么它记录的是当前线程执行处的指令地址,如果是本地方法,计数器则为空,该内存区域不会发生OutOfMemoryError异常
Java虚拟机栈
每个线程都对应有一个虚拟机栈,线程中执行的每个方法对应一个栈帧(Frame),由栈帧负责存储方法的各种信息,比如使用局部变量表来记录局部变量的类型、使用操作数栈来操作变量值、动态链接(在运行期间将符号引用转换为直接引用)、方法的返回值、异常表。在任何一个时刻,一个线程只有一个方法处于正在执行中,该方法对应的栈帧称为当前栈帧,随着方法的执行开始与结束对应到了栈帧的进入与退出,在方法返回时,当前栈帧将返回结果传回到上一个栈帧。如果线程所需的栈深度大于Java虚拟机所允许的深度,将引发StackOverflowError异常;如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存时将抛出OutOfMemoryError异常。Java虚拟机栈的内存区域也是线程私有的内存。
局部变量表:上面说它记录了变量的类型,具体有基本数据类型、对象引用、returnAddress(指令地址),每个变量对应一行记录(每行记录都有一个索引),周志明老师称之为变量槽,除了long、double类型占用两个变量槽之外,其他都只占用一个。局部变量表的长度是在编译器期就已经确定了,长度指的是变量槽的数量,而具体的数据结构大小完全由Java虚拟机实现来决定。如果使用对象来调用方法,那么指向当前对象的引用将占用局部变量表的第一个变量槽,也就是通常使用的
this变量,其他局部变量将从第二个变量槽开始存储,如果调用静态方法的话,自然就不需要该引用,所以局部变量还是从第一个变量槽开始构建。
操作数栈:其最大长度也是在编译期就决定好了,Java虚拟机提供了将局部变量的常量或值加载到操作数栈上的指令,同时也有从操作数栈上获取值的指令,它可以保存任何类型的值,只不过要用适当其类型的方式进行操作,比如说往操作数栈上推入float类型的值,结果却使用iadd指令(操作int类型的值)进行操作。
为了方便理解,如图所示:

堆
堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,其内存大小可以是固定的,也可以动态扩展,该内存区域用于存放对象实例。由于被创建的对象不会主动释放,所以通常由垃圾收集器来管理这块区域,经常会听到老年代、新生代的词语,其实《Java虚拟机规范》中并未提及这些概念,而是由具体的实现者根据需要自定决定堆的布局与垃圾收集器技术,出现这些术语通常指的是HotSpot虚拟机。如果在堆中没有内存可以分配给对象实例,并且堆也无法在扩展时,将会抛出OutOfMemoryError异常。
本地方法栈
本地方法栈与虚拟机栈所发挥的作用是类似的,《Java虚拟机规范》并未对它有任何的规定,在栈深度溢出或者扩展失败时分别抛出StackOverflowError异常和OutOfMemoryError异常。
方法区
方法区是被所有线程共享的一块内存区域,在虚拟机启动时创建,其内存大小可以是固定的,也可以动态扩展,存储类型信息(类的版本、字段、方法、接口信息、常量池表)、常量、静态变量、运行时常量池、已编译后的代码等数据,《Java虚拟机规范》中并未对它有明确的规定,如果方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。针对HotSpot虚拟机实现来说,JDK8以前使用永久代来实现方法区,到了JDK8时提出了元空间的概念来实现。
运行时常量池
运行时常量池是方法区的一部分,是常量池表的运行时表示,其中常量池表存放编译期生成的各种字面量与符号引用,将在类加载后存放到运行时常量池中,《Java虚拟机规范》中并未对它有明确的规定。既然其属于方法区,那么自然也会发生OutOfMemoryError异常。
总结
关于运行时数据区的知识点参考了周志明老师的书籍及《Java虚拟机规范》进行归纳总结,对《Java虚拟机规范》没有兴趣的读者可以直接阅读周志明老师的书,个人感觉说的很好,基本上都提到了规范的内容。
浙公网安备 33010602011771号