Java虚拟机运行时数据区域

虚拟机的学习知识点较为抽象,一直想对各个模块进行系统的理解,希望这次以博文的形式回顾能够有更多的收获,这里也只针对主流HotSpot虚拟机进行介绍。本篇主要探究虚拟机运行时数据区域,

运行时数据区域描述的是什么

通常我们将程序发布在Tomcat等容器下启动并运行,对虚拟机而言这是一个它需要进行管理的进程,我们可以通过jconsole或者jvisulvm(jdk自带)查看进程的运行和资源占用情况。每个在虚拟机中运行的进程都有统一的运行模式,在内存中是怎样的一个概念模型,就好像工厂,需要有仓库,车间,操作员这些组件保证了工厂的正常运行。那么运行时数据区域描述的就是java进程在内存中的各个部分是如何协调工作的。

运行时数据分区

java虚拟机将每个java进程运行时数据区域分为几个主要的分区:虚拟机栈(java+native),堆,程序计数器,直接内存,方法区(包括运行时常量池)。这些区域都存在于操作系统内存中,各自在java进程运行过程中发挥不同的作用。
cmd-markdown-logo

Java堆

Java堆可以理解为工厂中的仓库,仓库提供货物。java堆提供数据源,所有的实体对象都是存储在java堆中。每个货物都存有自己的标签(唯一标识),记录由哪个模板机器加工(方便找到原始零件信息),以及自己的价值部分。对象也有自己的MarkWord记录自己的哈希码,GC年龄以及该对象的锁信息(根据锁类型不同存储不同的信息),指向类类型的指针(元数据信息),数据部分(基本数据类型和引用类型)。前两部分在32和64位操作系统中各占一个字长。这里我们可以对堆进行一个形象的描述:
cmd-markdown-logo

虚拟机栈

虚拟机栈可以理解为工厂中的多个车间,每个车间对应于一个栈帧,每个栈帧对应于一个java方法。虚拟机栈每个栈帧都包括局部变量表,操作数栈,动态链接,方法出口。

局部变量表

局部变量表存放了编译器可知的各种基本数据类型(int,short,long,float,double,boolean,byte,char),引用类型(referrence,到堆中对象的指针地址或者指向指定直接指令的地址)。局部变量表中以Blot为基本存储单位,一个Blot大小为32bit,long和double需要两个Blot进行存储其他基本变量只需要一个。局部变量表的大小是在编译期就确定的,在调用栈帧的时候直接按照编译期确定的大小进行分配,不会再进行变动。

操作数栈

操作数栈可以帮助处理所有的计算,在编译原理层面可以有多种实现,较为经典的算符优先法等,之后的博文有机会会介绍一下。Java的操作数栈实际上也是一个先入后出的栈,栈的深度在编译期的时候有Max_stacks的最大值确定,一个Slot计为一个深度,long和double类型两个深度。当遇到JIT解释器字节码iadd指令时会将栈顶两个元素pop出来执行之后add回去,直到算符栈执行完毕,典型的算符优先法。

动态连接

动态连接是针对动态分派的一个概念,在java虚拟机栈栈帧的调用过程中,每个栈帧会记录到运行时常量池中的一个方法的符号引用(未确定具体引用方法)地址。符号引用根据运行时具体的数据类型分派的指定的类元方法数据,也就是转化为直接引用(确定具体的引用方法)。这也是java重写之后能定位到具体数据类型的方法,而不会去调用被重写的方法。

方法返回地址

方法返回地址结合程序计数器,因为栈帧在实际的调用过程中是不断嵌套的,这样的调用需要记录嵌套调用之后需要返回到上一个栈帧的某个地址,否则虚拟机栈无法正常执行。

本地方法栈

本地方法栈(Native Method Stack)是类似于java虚拟机栈的部分,不同的是本地方法栈主要用于执行C++方法,这部分区域不属于java虚拟机分配的内存区域,受限于操作系统内存。本地方法栈和直接内存区域需要提供一定的操作系统物理内存保证java进程的正常运行,在分配堆-Xmx和-XX:MaxPermSize时候如果动态扩展超出操作系统物理内存大小将导致Native Method Area OOM异常。

方法区

方法区Method Area通常都被理解为“永久代”,类的版本,字段,方法,接口等元数据信息都存储在这个区域。这个区域在jdk1.7之前需要单独分配空间,jdk1.7将方法区的运行时常量池移到了堆中,jdk1.8将方法区移到了直接内存区域,不属于虚拟机直接分配的区域受限于操作系统内存大小。可以通过-XX:MaxMetaSpace元空间大小,超过这个会抛出OOM异常。

直接内存

直接内存Direct Memory区域可以跟本地方法理解都是为Native方法执行提供的内存区域,这部分也会抛出OOM异常。

运行时常量池

运行时常量池Runtime Constant Pool主要用于存放编译期生成的各种字面量和符号引用,有部分符号引用会直接转化为直接引用,部分符号引用需要根据实际类型动态分派到直接引用。

总结

了解Java运行时数据区域,对于之后虚拟机的深刻理解有很重要的作用。运行时数据区域可以帮助理解java并发内存模型和java的垃圾回收机制。