JVM内存详解
Java虚拟机的运行时内存空间可以分成五个部分:
- 程序计数器
- 虚拟机栈
- 本地方法栈
- 堆
- 方法区
内存划分示意图:
程序计数器
作用
- 当前线程所执行的字节码的行号指示器。
- 通过改变程序计数器的值来选取下一条要执行的字节码指令。
- 程序的分支,跳转,循环,异常处理,线程恢复依赖于程序计数器完成。
特性
- 是线程私有的,记录线程当前执行的位置。
- 若线程执行的是Java方法,则程序计数器记录的是字节码指令的地址;若执行的是Native方法,则计数器值为空。
虚拟机栈
定义
- 描述方法执行的内存模型。
- 虚拟机栈中保存着一个个栈帧,一个栈帧对应于一个方法。
- 一个方法被线程执行,则JVM创建一个栈帧,并将栈帧入栈;若一个方法执行完成,则对应的栈帧从虚拟机栈中出栈。
- 每个栈中包含的内容:
- 局部变量表
- 操作数栈
- 动态链接
- 方法出口
特性
- 虚拟机栈是线程私有的,一个线程拥有一个虚拟机栈。
局部变量表
- 局部变量表存放编译时期可知的:
- 基本数据类型
- 对象引用
- returnAddress类型
- 局部变量表所需的内存空间在编译时期完成分配,完全确定,运行时不改变。
- 对象引用:(也叫reference类型),本质是一个地址,指向一个对象的起始地址或一个代表对象的句柄。
- returnAddress类型:本质是一个地址,指向一条字节码执行。
可能的异常
StackOverFlow
异常:请求的栈深度大于允许的最大深度。OutOfMemory
异常,内存已用完,且无法扩展栈深度。
本地方法栈
- 类似于虚拟机栈,是用来存放Native方法的相关信息。
堆
定义
- 用来存放大部分的对象实例和数组。
特性
- 在堆中声明的对象不能直接访问,需通过虚拟机栈中对应的引用变量访问堆中的对象或数组。
- 所有线程共享的内存区域。
- 占JVM内存中的最大一部分。
- 物理上非连续,逻辑上连续。
- 垃圾回收的主要区域,也称GC堆。
可能的异常
OutOfMemoryError
异常:内存用完了。
方法区
定义
- 用来存储已被JVM加载的:
- 类的信息
- 常量
- 静态变量
- 编译后的代码
特性
- 是各个线程共享的内存区域。
- 物理上不需要连续的内存空间。
- 大小可以固定,也可扩展。
- 可以不实现垃圾回收(数据非永久存在,回收主要是常量池的回收和类型的卸载)。
运行时常量池
定义 常量池中存放编译时期产生的各种字面量和符号引用。
特性
- 可以在运行时将新的常量放入方法区的运行时常量池中。
如:
String
类中的intern
方法:若常量池中已包含指定的字符串,则返回常量池中的字符串;否则,将指定的字符串加入常量池中,并返回此字符串对象的引用。
可能的异常
OutOfMemoryError
异常:常量池无法申请内存时。
直接内存
NIO引入了一种基于通道和缓冲区的IO方式,它可以使用本地函数直接分配堆外内存,然后通过一个存储在堆里的DirectByteBuffer对象作为这块内存的引用来操作堆外内存中的数据。
直接内存不受Java堆大小的限制,但仍然受本机总内存的限制。
可能的异常
- OutOfMemoryError异常
class son extends father{ // 子类,存放于堆中
static int var1; // 静态成员变量,存放于方法区
static father var2; // 静态成员变量,存放于堆中
final double var3=3.14; // 常量,存放于方法区
void method1(){ // 方法,存放于虚拟机栈中
int var4; // 局部变量,存放于虚拟机栈的局部变量表中
father var5 = new father(); // 局部引用变量var5存放于虚拟机栈的局部变量表中,指向的对象存放在堆中
}
}
class father{
static int var1;
void method1(){
int var2;
}
}
JDK8的改动
- 取消“永久代”,使用“元空间”(metaspace)。
- 静态成员变量在堆中。
- 方法区是JVM规范,永久区是方法区的具体实现。
内存的划分:
- 虚拟机内存:
- 程序计数器
- 虚拟机栈
- 本地方法栈
- 堆(常量池,静态成员变量,对象,数组)
- 本地内存
- 元空间
参考: