1 JVM内存模型
2 程序计数器(PC)
每个线程都会有自己私有的程序计数器(PC)。可以看作是当前线程所执行的字节码的行号指示器。
也可以理解为下一条将要执行的指令的地址或者行号。字节码解释器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、 循环、 跳转、 异常处理、 线程上下文切换,线程恢复时,都要依赖PC.
- 如果线程正在执行的是一个Java方法,PC值为正在执行的虚拟机字节码指令的地址
- 如果线程正在执行的是Native方法,PC值为空(未定义)
3 虚拟机栈(VM Stack)
VM Stack也是线程私有的区域。他是java方法执行时的字典:它里面记录了局部变量表、 操作数栈、 动态链接、 方法出口等信息。
在《java虚拟机规范》一书中对这部分的描述如下:
栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接 (Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。
栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。
栈帧的存储空间分配在 Java 虚拟机栈( §2.5.5)之中,每一个栈帧都有自己的局部变量表( Local Variables, §2.6.1)、操作数栈( OperandStack, §2.6.2)和指向当前方法所属的类的运行时常量池( §2.5.5)的引用。
说白了,VM Stack是一个
栈
,也是一块
内存区域
。
所以,他是有大小的。虽然有大小,但是一般而言,各种虚拟机的实现都支持动态扩展这部分内存。
- 如果线程请求的栈深度太大,则抛出
StackOverflowError
- 如果动态扩展时没有足够的大小,则抛出
OutOfMemoryError
4 本地方法栈(Native Method Stack)
Java 虚拟机实现可能会使用到传统的栈(通常称之为“ C Stacks”)来支持 native 方法( 指使用 Java 以外的其他语言编写的方法)的执行,这个栈就是本地方法栈( Native MethodStack)。
VM Stack是为执行java方法服务的,此处的Native Method Stack是为执行本地方法服务的。
此处的本地方法指定是和具体的底层操作系统层面相关的接口调用了(这部分太高高级了,不想深究……)。
《java虚拟机规范》中没有对这部分做具体的规定。所以就由VM的实现者自由发挥了。
有的虚拟机(比如HotSpot)将VM Stack和Native Method Stack合二为一,所以VM的另一种内存区域图就如下面所示了:
Java堆(Heap)
在 Java 虚拟机中,堆( Heap)是可供各条线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域。
以下是本人对《java虚拟机规范》一书中对Java堆的介绍的总结:
- 在虚拟机启动的时候就被创建
- 是所有线程共享的内存区域
- 存储了被自动内存管理系统所管理的各种对象
- 这些受管理的对象无需,也无法显式地被销毁
- 自动内存管理系统:Automatic StorageManagement System,也即是常说的"Garbage Collector(垃圾收集器)"
- 并未指明用什么具体的技术去实现自动内存管理系统
- Java 堆的容量可以是固定大小的,也可以随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩
- Java 堆所使用的内存不需要保证是连续的
- 如果实际所需的堆超过了自动内存管理系统能提供的最大容量,那 Java 虚拟机将会抛出一个
OutOfMemoryError
异常
- 实现者应当提供给程序员或者最终用户调节 Java 堆初始容量的手段
- 对于可以动态扩展和收缩 Java 堆来说,则应当提供调节其最大、最小容量的手段
- 所有的对象实例以及数组都要在堆上分配
6 方法区(Method Area)
方法区是由所有线程共享的内存区域。
方法区存储的大致内容如下:
- 每一个类的结构信息
- 运行时常量池( Runtime Constant Pool)
- 字段和方法数据
- 构造函数和普通方法的字节码内容
- 类、实例、接口初始化时用到的特殊方法
每一个运行时常量池都分配在 Java 虚拟机的方法区之中,在类和接口被加载到虚拟机后,对应的运行时常量池就被创建出来。
- 当创建类或接口的时候,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,那 Java 虚拟机将会抛出一个
OutOfMemoryError
异常。
7 直接内存(Direct Memory)
此处的直接内存并不是由JVM管理的内存。他是利用
本地方法库
直接在java堆之外申请的内存区域。
比如NIO中的
DirectByteBuffer
就是操作直接内存的。
直接内存的好处就是避免了在java堆和native堆直接同步数据的步骤。但是他并不是由JVM来管理的。