下面这些都是Java虚拟机规范,不是虚拟机具体实现
JVM内存可以划分为若干个不同的数据区域: 程序计数器,虚拟机栈,本地方法栈,堆,方法区
程序计数器占用的内存空间比较小,可以看做是当前线程所指向的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支操作、循环操作、跳转、异常处理等也都需要依赖程序计数器。
- 在Java虚拟机规范中,程序计数器没有规定OutOfMemoryError的情况;
- 程序计数器是线程私有的,每个线程内部都有一个私有的程序计数器。它的生命周期是和线程的生命周期是同步的;
- 当一个线程正在执行一个Java方法的时候,这个程序计数器记录的是正在执行的虚拟机字节码指令的地址。如果正在执行的是Native方法,则计数器值为空(Undefined)
虚拟机栈也是线程私有,生命周期与线程同步。在Java虚拟机规范中,虚拟机栈有两种异常情况:
- StackOverflowError 当线程请求栈深度超出虚拟机栈所允许的深度时抛出
- OutOfMemoryError 当Java虚拟机动态扩展到无法申请足够内存时抛出
JVM是基于栈的解释器执行的,DVM是基于寄存器解释器执行的。
上面这个栈就是指的虚拟机栈,虚拟机栈的初衷是用来描述Java方法执行的内存模型,每个方法被执行的时候,JVM都会在虚拟机栈中创建一个栈帧。
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,每个线程在执行某个方法时,都会为这个方法创建一个栈帧。 一个线程包含多个栈帧,而每个栈帧内部包含:局部变量表、操作数栈、动态链接、返回地址等。
局部变量表
局部变量表是变量值的存储空间,调用方法时传递的参数,以及在方法内部创建的局部变量都保存在局部变量表中。在Java编译成class文件的时候,就会在方法的Code属性表中的max_locals数据项中,确定该方法需要分配的最大局部变量表的容量。
系统不会为局部变量赋予初始值
操作数栈
操作数栈也常称为操作栈,它是一个后入先出栈(LIFO)。
同局部变量表一样,操作数栈的最大深度也在编译的时候写入方法的Code属性表中的mac_stacks数据项中。栈中的元素可以是任意Java数据类型,包括long和double。
方法执行过程中,会将各种字节码指令压入和弹出操作数栈。
动态链接
动态链接的主要目的是为了支持方法调用过程中的动态链接。
返回地址
一个方法开始,只有两种方式可以退出这个方法:
- 正常退出:指方法中的代码正常完成,或者遇到任意一个方法返回的字节码指令(如return)并退出,没有抛出任何异常
- 异常退出:指方法执行过程中遇到异常,并且这个异常在方法体内部没有得到处理,导致方法提出。
不管是什么方式退出,在方法退出后都需要返回到方法被调用的位置,程序才能继续执行。虚拟机栈中的返回地址就是用来帮助当前方法恢复它的上层方法执行状态。
本地方法栈和虚拟机栈基本是相同的,只不过是针对native方法。在有些虚拟机中已经将两个合二为一了(如HotSpot)。
Java堆是JVM所管理的内存中最大的一块,该区域唯一目的就是存放对象实例,几乎所有对象的实例都在堆里面分配,因此它也是Java垃圾收集器(GC)管理的主要区域,有时也叫GC堆。同时它也是所有线程共享的内存区域,因此被动态分配在此区域的对象如果被多个线程访问的话,需要考虑线程安全问题。
按照对象存储时间的不同,堆中的内存可以划分为新生代(Young)和老年代(Old),其中新生代又被划分为Eden和Survivor区。不同区域的对象生命周期不同,这样可以使用不同的垃圾回收算法,具有针对性,垃圾回收效率更高。
方法区:主要存储已经被JVM加载的类信息(版本、字段、方法、接口)、常量、静态变量、即时编译器编译后的代码和数据。该区域和堆一样,也是被各个线程共享的内存区域。
运行时常量池也是方法区中的,它用于存放编译期生成的各种字面量与符号引用。


