Java 在 JVM 帮助下无需自行管理内存,但一旦出现内存泄漏或溢出问题,在不了解 JVM 怎么使用内存的情况下,很难排查异常、修正问题;
文章目录
1. 程序计数器(Program Counter Register)
可以看作是当前程序执行的字节码的行号指示器;它是程序控制流的指示器,分支
、循环
、跳转
、异常处理
、线程恢复
等基本功能都需要依赖程序计数器;
线程私有
,每个线程有一个独立的程序计数器,独立存储各自线程的执行位置;
当线程正在执行的是本地(Native)方法,程序计数器会指向空(Undefined);
程序计数器是《Java 虚拟机规范》中唯一没有规定任何 OutOfMemoryError
的区域;
2. Java 虚拟机栈(Java Virtual Machine Stack)
Java 虚拟机栈描述的是 Java 方法执行的线程内存模型(线程私有):Java 虚拟机栈为每个被执行的方法创建一个栈帧(Stack Frame:局部变量表、操作数栈、动态连接、方法出口等信息);一个栈帧在虚拟机栈中的入栈出栈就对应了一个方法的开始执行与执行完毕;
通常讲到的栈
就是指这里的 Java 虚拟机栈、或只是指 Java VM Stack 中局部变量表部分;
局部变量表存放了编译器可知的 JVM 基本数据类型
(boolean、byte、char、short、int、float、long、double)、对象引用
(reference 类型,可能是指向对象起始地址的引用指针,或者代表对象的句柄,或者对象相关位置)、returnAddress 类型
(指向一条字节码指令的地址);
数据存储在局部变量表的局部变量槽
(Slot,一般占用 32 bit,不同 VM 实际实现可能不同)中,long 和 double 占用 2 个 Slot,其余占用 1 个;
局部变量表的 Slot 数在编译期间完全确定,在方法运行期间不会改变;
StackOverflowError
: 线程请求的栈深度大于虚拟机允许的深度时抛出;OutOfMemoryError
: 如果 JVM 允许栈容量动态扩展,当栈扩展时无法申请到足够的内存时抛出;
3. 本地方法栈(Native Method Stack)
与 Java VM Stack 类似,只是 Java VM Stack 是为执行 Java 方法服务,而本地方法栈是为本地(Native)方法服务;
4. Java 堆(Heap)
唯一目的是存放对象实例,JVM 管理的最大一块内存,线程共享
;几乎
所有对象实例(对象实例、数组)都是存放在堆中,但随着即时编译技术的进步,如逃逸分析
、栈上分配
、标量替换
等优化手段导致并不那么绝对了;
从内存回收的角度讲,Java 堆经常被划分为多个区(经典分代:新生代
、老年代
、永久代
、Eden 区
、From Survivor
、To Survivor
);如 G1 的新垃圾收集器不采用分代设计;
从内存分配的角度讲,Java 堆被划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升内存分配效率;
OutOfMemoryError
: Java 堆中没有足够内存完成实例分配,切对无法再扩展时抛出;
5. 方法区(Method Area)
别名非堆
(Non-Heap);用于存储已被 JVM 加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据;线程共享
;方法区是一块逻辑区域,其实现可以是永久代
和元空间
;
永久代
仅仅是 JDK 8 以前 HotSpot VM 使用来实现方法区的,这使得 HotSpot 的 GC 可以像管理 Java Heap 一样管理这部分内存;但其他 VM 是不存在永久代的概念的;JDK 6 开始 HotSpot 计划改用本地内存(Native Memory)实现方法区,JDK 7 HotSpot 将永久代的字符串常量池、静态变量等移出至堆中;JDK 8 HotSpot 彻底移除永久代,改用以本地内存实现的元空间
(Metaspace:主要承接 JDK 7 中永久代剩余的内容,如类型信息)来替代;
OutOfMemoryError
: 方法区无法满足新的内存分配需求时抛出;
6. 运行时常量池(Runtime Constant Pool)
方法区的一部分,编译期
生成的各种字面量与符号引用存放在 Class 文件的常量池表(Constant Pool Table),而这部分内容(以及直接引用)在类加载后
存放在方法区的运行时常量池中;
Class 文件中包含类的版本、字段、方法、接口等描述信息,以及常量池表;
相比 Class 文件的常量池表,运行时常量池具备动态性,运行期间可以将新的常量放入运行时常量池(如 String 类的 intern() 方法)
OutOfMemoryError
: 受方法区内存限制,当常量池无法再申请到内存时抛出;
7. 直接内存(Direct Memory)
并不是运行时数据区的一部分,但是会被频繁使用;
JDK 4 加入的 NIO(New Input/Output,一种基于 Channel 和 Buffer 的 I/O 方式,可以使用 Native 库直接分配堆外内存,然后通过 Java 堆的 DirectByteBuffer 对象作为这块内存的引用进行操作,避免了 Java 堆和 Native 堆来回复制数据)类就使用了直接内存;
OutOfMemoryError
: 直接内存不受 Java 堆大小限制,受本机总内存以及处理器寻址空间的限制,当各个内存区域总和大于物理内存限制时抛出;
上一篇:Java 技术倾向
下一篇:「JVM 内存管理」HotSpot VM 对象概要
PS:感谢每一位志同道合者的阅读,欢迎关注、评论、赞!
参考资料:
- [1]《深入理解 Java 虚拟机》