初识 JVM 内存区域划分
?
大多数 JVM 将内存区域划分为?MethodArea(Non-Heap),?Heap,?ProgramCounter Register,?Java Method Stack,NativeMethod Stack?和?DirectMemomry(注意 DirectoryMemory 并不属于 JVM 管理的内存区域)。前三者一般译为:方法区、堆、程序计数器。但不同的资料和书籍上对于后三者的中文译名不尽相同,这里将它们分别译作:Java 方法栈、原生方法栈和直接内存区。对于不同的 JVM,内存区域划分可能会有所差异,比如 Hot Spot 就将Java 方法栈和原生方法栈合二为一,我们可以同城为方法栈(Method Stack)。
?
首先我们熟悉一下一个一般性的 Java 程序的工作过程。一个 Java 源程序文件,会被编译为字节码文件(以 class 为扩展名),然后告知 JVM 程序的运行入口,再被 JVM 通过字节码解释器加载运行。那么程序开始运行后,都是如何涉及到各内存区域的呢?
?
概括地说来,JVM 每遇到一个线程,就为其分配一个程序计数器、Java 方法栈和原生方法栈。当线程终止时,两者所占用的内存空间也会被释放掉。栈中存储的是栈帧,可以说每个栈帧对应一个“运行现场”。在每个“运行现场”中,如果出现了一个局部对象,则它的实例数据被保存在堆中,而类数据被保存在方法区。
?
我们用上面这一小段文字就描述完了每个内存区域的基本功能。但是这还比较粗糙,下面分别介绍它们的存储对象、生存期与空间管理策略。
?
2.1 程序计数器 ?
线程特性:私有
?
存储内容:字节码文件指令地址(Java Methods),或Undefined(Native Methods)
?
生命周期:随线程而生死
?
空间策略:占用内存很小
?
这个最简单,就先捡它说吧。程序计数器,是线程私有(与线程共享相对)的,也就是说有 N 个线程,JVM就会分配 N 个程序计数器。如果当前线程在执行一个 Java 方法,则程序计数器记录着该线程所执行的字节码文件中的指令地址。如果线程执行的是一个 Native 方法,则计数器值为Undefined。
?
程序计数器的生存期多长呢?显然程序计数器是伴随线程生而生,伴随线程死而死的。而它所占用的内存空间也很小。
?
2.2 Java 方法栈与原生方法栈 ?
Java 方法栈也是线程私有的,每个 Java 方法栈都是由一个个栈帧组成的,每个栈帧是一个方法运行期的基础数据结构,它存储着局部变量表、操作数栈、动态链接、方法出口等信息。当线程调用调用了一个 Java 方法时,一个栈帧就被压入(push)到相应的Java 方法栈。当线程从一个 Java 方法返回时,相应的 Java 方法栈就弹出(pop)一个栈帧。
?
其中要详细介绍的是局部变量表,它保存者各种基本数据类型和对象引用(Object reference)。基本数据类型包括 boolean、byte、char、short、int、long、float、double。对象引用,本质就是一个地址(也可以说是一个“指针”),该地址是堆中的一个地址,通过这个地址可以找到相应的 Object(注意是“找到”,原因会在下面解释)。而这个地址找到相应 Object 的方式有两种。一种是该地址存储着 Pointer to Object Instance Data 和 Pointer to Object Class Data,另一种是该地址存储着 Object Instance Data,其中又包含有 Pointer to Object Class Data。