JVM
类加载器将字节码文件加载到内存当中的时候,会用到两块内存区域:一块是方法区、另外一块叫做堆区,这两块区域都属于Java虚拟机管理的内存,由于JVN在运行过程中会去使用这块内存,所以就称之为运行时数据区域,它的职责除了保存字节码信息之外,还可以将创建出来的对象放到这块内存区域。
一、 运行时数据区
通常把运行时数据区换分为两类,一类是线程不共享(程序计数器、Java虚拟机栈、本地方法栈)、一类是线程共享(方法区、堆区)
- 线程不共享指的是每当创建一个线程之后,每个线程里都会有一份程序计数器、Java虚拟机栈和本地方法栈对应的数据,该数据由本线程维护,其他线程无法访问本线程对应的数据,所以数据无法做到共享,但是整体安全性比较高,当线程结束后,将整个线程回收掉,这块内存区域也会得到释放。
- 线程共享指的是只要往方法区/堆区放入数据,每个线程都可以获取这些数据并去使用,虽然数据可以共享,但是存在安全性问题
二、 程序计数器
程序计数器(Program Counter Register)也叫PC寄存器,每个线程会通过程序计数器记录当前要执行的的字节码指令的地址。
- 属于线程独自拥有的数据,里面存放了接下来要执行的字节码指令的内存地址。
- 字节码指令最初是保存在字节码文件中的,类加载器把字节码文件读取到内存之后,这个指令也会保存到内存当中,每一行指令都有对应的内存地址。
- 字节码指令最终是要被解释器解释执行的,所以解释器就需要知道要执行的字节码指令在哪,而程序计数器就恰恰保存了字节码指令的地址,解释器只需要通过程序计数器就能得到字节码指令的内存地址。
程序计数器的案例
- 源代码
public static void main(String[] args) { int i = 0; if (i == 0) { i--; } i++; }
- 字节码指令
0 iconst_0 1 istore_1 2 iload_1 3 ifne 9 (+6) 6 iinc 1 by -1 9 iinc 1 by 1 12 return
将局部变量 i 赋值为0
- iconst_0
- istore_1
判断 i 和0是否相等,如果不相等,跳转到指令9的位置
- iload_1
- ifne 9 (+6)
i 和 0 相等,就将i减1:
- iinc 1 by -1
i 和 0 不相等,就将 i 加1:
- iinc 1 by 1
接下来这个字节码指令就会进入类的加载阶段被加载到内存中,原来字节码指令的每条指令前都有一个偏移量,加载到内存后,偏移量会被替换成地址,每一条字节码指令都会有自己的内存地址,在代码执行过程中,程序计数器会记录下一行字节码指令的地址。执行完当前指令之后,虚拟机的执行引擎根据程序计数器执行下一行指令。
程序计数器可以控制程序指令的进行,实现分支、跳转、异常等逻辑。
在多线程执行情况下,Jva虚拟机需要通过程序计数器记录CPU切换前解释执行到那一句指令并继续解释运行。