起步
哟西,一星期一更的系列闪亮登场! 参考书籍:“深入理解java虚拟机”
经常有人把java内存区域笼统地划分为堆内存(Heap)和栈内存(Stack),这种划分方式直接继承自传统的C、C++程序的内存布局结构,在Java语言里就显得有些粗糙,实际的内存区域划分要比这更复杂。
栈:线程独占的内存区域(按照线程划分内存区域的方式),它可以细分为程序计数器、Java虚拟机栈、本地方法栈这三块区域。
程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
在java虚拟机的概念模型中,字节码解释器(这个在之后的篇幅中会有详细说明)工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于虚拟机中的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
如果线程正在执行的是一个java方法, 这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器的值则为空(Undefined)。
而且,程序计数器是JVM中明确规定是没有OOM情况出现的一块内存区域(很自然就可以理解)
Java虚拟机栈
虚拟机栈是java方法执行的线程内存模型:每个方法被执行的时候,java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表
局部变量表中存放了编译期间可知的各种java虚拟机基本数据类型(byte、short、int、long、float、double、char、boolean),对象引用(refrence类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
这些数据库类型在局部变量表中的存储空间都是以局部变量槽(Slot)来表示的。以下是对局部变量槽的一些描述:
- 参数的存放总是在局部变量数组的index0开始,到数组长度-1的索引结束。
- 在局部变量表中,32位以内的类型只占用一个slot(包括returnAddress类型),64位的类型(long和double)占用两个slot。 byte、short、char在存储栈被转换为int、double也被转换为int,0表示false,非0表示true。
- slot变量槽的复用:当一个变量的作用域不能覆盖整个方法时,那么这个变量的变量槽会被重复使用。
操作数栈
每一个独立的栈帧中除了包含局部变量表以外,还包含一个后进先出的队列,这个队列就叫做操作数栈。操作数栈,在方法的执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)/出栈(pop)
方法返回地址
栈帧中的每一个方法都会有一个方法返回地址,如果一个方法是正常退出的话,那么这个方法返回地址会返回下到到该方法被调用的位置。方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。
动态链接
- 每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。
- 在java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在class文件的常量池里。比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
- 包含这个引用的目的就是为了支持当前方法的代码能够实现“动态链接”。比如:invokedynamic指令
- 静态绑定和动态绑定:
- 静态绑定:如果一个方法的引用在编译期就能确定是使用那个方法,并在运行的时候也是执行保持不变时。这种情况下调用方法的符号引用你转换为直接引用的过程称之为静态链接。
- 动态链接:如果被调用的方法在编译器无法被确定下来,也就是说,只能够在程序运行期调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因为也就被称之为动态链接。
附加信息
例如与调试、性能收集相关的信息。
本地方法栈
本地方法栈(Native Method Stacks)与虚拟机所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行java方法(也就是字节码服务),而本地方法栈则是为虚拟机使用到的本地(Native)方法服务
JVM相关的文章有点难以编写,每块内存区域都涉及到非常多的知识点,这个系列的前几篇是对一些基本概念的描述,后面会设计到一些深入内容的讲解。
|