JVM会在会在执行Java程序过程中把所管理的内存划分为若干区域,主要包括程序计数器(Program Counter Register),虚拟机栈(VM Stack),本地方法栈(Native Method Stack),堆区(Heap)以及方法区(Method Area)。其中前面3个是线程隔离的数据区,即各个线程均有一份,而后两者是共享区,即所有线程均共享同一份。接下来,我们分别来看一下这些线程隔离的数据区。
首先是程序计数器。
用于指示一个线程运行到哪个地方了,因为多个线程时,A线程有可能会被挂起从而转向运行B线程,那么等返回执行A线程时,JVM怎么记录A运行到哪里了呢?答案就是程序计数器,因此程序计数器是线程隔离的。那么计数器里存储的是什么东西呢?
首先我们要大概了解以下字节码文件长什么样子。
public void F(){ // 原来的F方法内部的 java 代码,被翻译为下面的类似于汇编语言的指令 0 xxxx .... 2 xxxx .... 4 xx ... 5 xxx ... }
代码中的0、2、4、5是字节码的行号,程序计数器存储的正是字节码行号。所以程序计数器的运行原理就很明朗了。当然程序计数器是会被和线程一同创建的。程序计数器是唯一一个没有OutOfMemory异常的区域,因为它不会增加空间,不过随着程序的运行会改变。
接着是虚拟机栈。
我们常说的堆内存、栈内存的栈区一般指的就是这个,虚拟机栈被用于描述java的方法。每个java方法在执行时会创建一个栈帧(Stack Frame),栈帧的结构包括局部变量表,操作数栈,动态链接和方法出口几个部分。方法执行时创建栈帧,并进入虚拟机栈中,调用结束时,栈帧销毁。每个方法从调用到结束对应栈帧在虚拟机栈的声明周期。虚拟机栈是线程隔离的。
若栈帧一直增加,超过所允许的深度,将会导致StackOverflowError,可以想象如果方法无限递归没有出口就会导致该异常。
如果虚拟机栈可以动态扩展但在扩展中无法申请到足够的内存,就会抛出OutOfMemoryError异常。
栈帧-局部变量表:存放方法参数和方法内部的局部变量,因此,基本数据类型和引用类型的引用就存在栈区中栈帧的局部变量表。(引用类型的对象本身在堆区)
本地方法栈。
是针对Native的方法的栈,虚拟机栈是其他java方法的栈。不同虚拟机可以自由实现,比如常用的HotSpot虚拟机就选择合并虚拟机栈和本地方法栈。