拒绝做知识“收藏家”
答应我,跟我一起学习吧,别再做知识收藏家了,把《深入理解 Java 虚拟机》书拿出来,翻它,盘它,磋磨它。
你创建的 Java 对象搁哪了
今天学习的内容是 JVM 自动内存管理中的 运行时数据区域
内容。
自动内存管理
Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。 —— 出自周志明《深入理解 Java 虚拟机》
Java 的内存区域与内存溢出。
对于 Java 程序来讲,内存是由虚拟机的内存自动管理机制垃圾回收来进行管理的。这样便使得程序不容易出现内存溢出和内存泄漏问题。但是一旦出现内存问题,排查和解决起来便不是一件容易的事。
运行时数据区域
- 程序计数器
- 虚拟机栈
- 本地方法栈
- 堆
- 方法区 - 运行时常量池
- 直接内存
PC寄存器(程序计数器)
- 线程私有
- 存储当前方法的字节码位置,如果是本地方法则存储 undefined
- 唯一一个不会出现 OOM 的位置
- 方法执行时使用;
Java 虚拟机栈
- 线程私有
- 存储程序方法执行时的 栈帧
- 达到栈允许最大容量抛出 StackOverflow Error 栈溢出
- 允许动态扩展,申请不到内存时抛出 OOM。(允许动态扩展由虚拟机实现方自行选择,hotspot选择的则是不允许动态扩展)
- 不需要保证连续的内存
- 方法执行时使用;
注:在 hotspot 中不允许扩展栈内存,但同样会发生 OOM ,这是发生在创建线程内存申请时内存不足抛出的OOM,这里也属于栈原因引起的。
本地方法栈
同上
注:说明两者服务对象,虚拟机栈服务于 Java 程序,本地方法栈服务于 JVM 程序(native 方法)。
堆
- 线程共享
- 存储类实例、数组对象
- 容量超过允许最大值时抛出 OOM 异常(允许动态扩展)
- 不需要保证连续的内存
- 创建对象时使用
方法区
- 线程共享
- 存储类的结构信息(方法、字段、构造函数)、运行时常量池
- 容量超过允许最大值时抛出 OOM 异常(允许动态扩展)
- 不需要保证连续的内存
- 虚拟机启动时创建
- 后被替换为元空间(这里的内容要和 hotspot 的“永久代“一起理解)
运行时常量池
(这部分内存区域同在方法区中体现)
- 线程共享
- 存储接口或类的常量池(字面量 与 符号引用
类的全部限定名等信息
) - 类加载时创建
- 创建过程使用的方法区内存大小,可能出现 OOM 异常
直接内存
(这部分为延展内容)
- 不受虚拟机参数控制,直接使用主机内存,受主机内存大小限制;
- JDK4中的 NIO 首次使用;
- 在设置JVM参数时,需考虑直接内存的使用大小,防止其过渡使用出现 OOM;
- JDK7的时候,使用直接内存实现了方法区,到 JDK8 将 JDK 7 剩余的类型信息移入元空间(这里指 hotspot 虚拟机)
| 作用\名称 | PC寄存器(程序计数器) | Java虚拟机、本地方法栈 | Java 堆 | 方法区 |
| —- | —- | —- | —- | —- |
| 异常情况 | 无 | 栈溢出、堆溢出 | 堆溢出 | 堆溢出 |
| 需要连续内存 | 否 | 否 | 否 | 否 |
| 存储内容 | 在执行 Java 代码时,存储字节码地址。
在执行本地方法时,存储 undefined | 栈帧(每个栈帧以方法为单位) | 类实例、数组对象 | 类的结构信息、字段、方法等 | | 使用时机 | 方法执行时 | 方法执行时 | 创建对象时 | 类被加载时 | | 线程私有 | 是 | 是 | 否 | 否 |
写在最后
Java 的对象搁哪现在你清楚了吗?每天一点点,啃完虚拟机。欢迎加群一起讨论学习,大家一起学习效率很高哦