注意:本文章内容是基于Java的HotSpot虚拟机
引言
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁
根据JVM规范,Java虚拟机所管理的内存包括以下几个运行时数据区域:
这里将方法区和堆划分为一种颜色是因为虚拟机的垃圾收集在这两个区域进行且它们是线程共享的
内存区域
程序计数器(Program Counter Register)
- 线程私有
- Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储
- 此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
虚拟机栈(VM Stack)
- 线程私有
- 虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息
- 为虚拟机执行Java方法(也就是字节码)服务
- 线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常
本地方法栈(Native Method Stack)
- 线程私有
- 与虚拟机栈所发挥的作用相似,为虚拟机所使用到的本地(Native)方法服务
- 与虚拟机栈一样,会抛出StackOverflowError和OutOfMemoryError异常
堆(Heap)
- 线程共享
- 唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存
- Java堆是垃圾收集器管理的主要区域,也称作“GC堆”
- 由于垃圾收集器采用分代收集算法,所以细分为:新生代和老年代
- 再细致分为:Eden空间、From Survivor空间、To Survivor空间
- Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,在实现时,既可以实现成固定大小的,也可以是可扩展的(通过-Xmx和-Xms控制)
- 如果在堆中没有完成实例分配,并且堆再也无法扩展时,将抛出OutOfMemoryError异常
方法区(Method Area)
注意:此内存区域只存在于JDK1.8之前的版本,在JDK1.8中撤销了永久代,取而代之的是元空间(Metaspace)
-
线程共享
-
在HotSpot虚拟机上又称永久代(PermGen space),只在HotSpot虚拟机才有,是HotSpot对JVM规范中方法区的实现
-
理论上上属于堆的一部分,但是为了与堆进行区分,通常又叫Non-Heap(非堆)
-
HotSpot使用永久代来实现方法区,其它虚拟机是不存在永久代的概念的
-
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
-
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
运行时常量池(Runtime Constant Pool)
- 方法区的一部分
- Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分类容将在类加载后进入方法区的运行时常量池存放
- 当常量池无法再申请到内存时抛出OutOfMemoryError异常
- 直接内存(Direct Memory)
- 不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域
- 使用Native函数直接分配堆外内存,不受Java堆大小的限制,受到本机总内存大小以及处理器寻址空间的限制
- 方法区的一部分
元空间
注意:由于本文参考资料《深入理解Java虚拟机:JVM高级特性与最佳实践》第2版是基于JDK1.7版本,所以上文中介绍了永久代的概念。而我本人所使用的是JDK1.8版本,所以HotSpot虚拟机中应该只存在元空间
- JDK1.7中已经将字符串常量池从永久代移除,在Java堆(Heap)中开辟了一块区域存放字符串常量池。而在JDK1.8中,已经彻底没有了永久代,将方法区直接放在一个与堆不相连的本地内存区域,这个区域被叫做元空间
- 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存
参考资料
《深入理解Java虚拟机:JVM高级特性与最佳实践》第2版 周志明 著
说明
JVM相关文章为读书笔记,内容来源于书本,仅供学习记录