《深入理解 JVM 虚拟机》读书笔记:第二章 Java 内存区域与内存溢出异常
2.1 JVM 运行时区域
JVM 在执行程序时会把它管理的内存分为不同的数据区域。这些区域有不同的用途、创建和销毁时间,有的随着进程启动而一直存在,有的依赖用户线程的启动和结束而建立和销毁。
2.2.1 程序计数器
程序计数器(Program Counter Register) 是一块较小的内存空间,可看作是当前线程所执行的字节码的行号指示器。在 JVM 概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
如果线程正执行的是 Java 方法,它记录的是正在执行的虚拟机字节码指令的地址;如果线程正执行的是本地方法,它的值应为空(undefined)
2.2.2 Java 虚拟机栈
与程序计数器一样,JVM stack 也是线程私有的,它的生命周期与线程相同。JVM 栈描述的是 Java 方法执行的线程内存模型:执行方法的时候,JVM 会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。局部变量表存放了编译器可知的各种 Java 虚拟机基本数据类型、对象引用和 returnAddress 类型(指向一条字节码指令的地址)
在《JVM 规范》中,对这个内存区域规定了两类异常:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常;如果 Java 虚拟机栈容量可动态扩展,当栈扩展时无法申请到足够的内存会抛出 OutOfMemoryError 异常。HotSpot JVM 的栈容量是不可以动态扩展的,只要线程申请成功了就不会有 OOM,但如果申请失败,仍然会出现 OOM。
2.2.3 本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈发挥的作用相似,区别是 JVM 栈为执行 Java 方法(也就是字节码)服务,本地方法栈为 JVM 使用到的本地方法服务。HotSpot 将本地方法栈和虚拟机栈合而为一。
2.2.4 Java 堆
对于 Java 应用程序来说,Java 堆是 JVM 管理的内存中最大的一块,它被所有线程共享,在 JVM 启动时创建,它的唯一目的是存放对象实例(注意,不是存放类定义),几乎所有对象都在这里分配内存。
Java 堆是垃圾收集器管理的内存区域,从分配内存的角度看,所有线程共享的 Java 堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)以提升对象分配时的效率。
不过,无论从哪个角度,无论如何划分,都不会改变 Java 堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例。
根据《JVM 规范》,Java 堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被看作连续的。
当前主流 JVM 都是可扩展的,如果 Java 堆中内存不足以完成对象分配而且堆也无法再扩展时,JVM 会抛出 OOM 异常
评论