最近在学习JVM,拜读了周志明的《深入理解Java虚拟机:JVM高级特性与最佳实践》,书中内容读后受益匪浅,让我对Java虚拟机有了完整的认识,这真是学习JVM的一本好书。结合自己的理解,整理一下笔记。
运行时数据区域
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。
如图:Java虚拟机运行时数据区
简单的理解为JVM为了方便数据的处理,把它的大内存分为了几个用途不同的小内存来方便Java程序的运行。
Java 虚拟机的内存模型分为两部分:一部分是线程共享的,包括 Java 堆和方法区;另一部分是线程私有的,包括虚拟机栈和本地方法栈,以及程序计数器这一小部分内存。
好了,到我们今天要讲的重点了
程序计数器
什么是程序计数器?
周志明在《深入理解Java虚拟机》中如是说:
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节 码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现) 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环跳转、 异常处理、线程恢复等基础功能都需要依赖这个计数器赖完成。
简单理解:
当程序启动时,尤其是多线程情况下,为了保证程序正常运行,为每一个线程配备一个程序计数器,通过程序计数器来为每个线程记录进度。
程序计数器的特点
线程私有
具有生命周期,随线程启动产生,线程结束消亡
唯一 一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
如果线程正在执行的是Java 方法,计数器记录的是正在执行的虚拟机字节码指令地址
如果正在执行的是Native 方法,则计数器记录值为空(Undefined)
我们知道Java虚拟机的多线程是通过线程轮流(涉及时间片轮转算法)切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。而每个线程都配备一个独立的程序计数器来确保线程切换后能恢复到正确的执行位置是很棒的一个做法。
通过代码来直观了解一下程序计数器
代码如下:
package practice7; public class Test { public int calc(){ int a = 3; int b = 7; int c = 2; return ( a + b ) * c; } }
我们将上面代码的java文件先编译成Class文件再使用 javap 反汇编工具看下class 文件中数据格式,如下图
图中已经指出字节码指令的偏移地址,偏移地址对应的iconst、bipush 等等是jvm 中的操作指令,这是入栈指令
当int类型 取值-1~5采用iconst指令 取值-128~127采用bipush指令。
当执行到方法calc()时在当前的线程中会创建相应的程序计数器,在计数器中为存放执行地址 (红框中的)0 2 3…等等
说明在我们程序运行过程中计数器中改变的只是值,而不会随着程序的运行需要更大的空间,也就不会发生溢出情况。
参考:JVM 程序计数器
内容较为粗糙宽泛,细细了解后会加深解析!