运行时数据区
参考 JDK 8 的官方文档,Java 虚拟机运行时数据区主要由以下部分组成:
- 程序计数器(Program Counter Register)
- Java 虚拟机栈(JVM Stack)
- 本地方法栈(Native Method Stack)
- Java 堆(Java Heap)
- 方法区(Method Area)
- 运行时常量池(Runtime Constant Pool)
程序计数器
- 程序计数器是一块较小的内存空间,用于指示当前线程执行字节码的行号;
- 程序计数器是线程独享的存储空间,生命周期与线程相同;
- 每个线程都有独立的程序计数器,互不干扰,是实现线程并发的基础条件。
当线程正在执行 Java 方法时,PC 指向字节码指令地址;当执行的是 Native 方法时,指针为空(Undefined)。
此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。
Java 虚拟机栈
本地方法栈
- 线程私有空间,生命周期与线程相同;
- 功能与 Java 虚拟机栈相似,区别是本地方法栈服务对象是 Native 方法;
- 相似条件下也会抛出 StackOverflowError 和 OutOfMemoryError 异常。
Java 堆
- 线程共享空间,生命周期与虚拟机相同;
- 虚拟机所有的实例
几乎
都在 Java 堆上分配; - Java 堆存放的空间不要求物理上的连续,逻辑上连续即可;
- 当 Java 堆内存不足以分配实例且无法再扩展时,会抛出 OutOfMemoryError 异常。
方法区
- 线程共享空间,生命周期与虚拟机相同;
- 用于存放虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;
- 当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常。
早期的 JDK 中,HotSpot 虚拟机选择把收集器的分代设计扩展至方法区,或者说使用永久代来实现方法区。回过头来看,这并不是一个好主意,这种设计导致了 Java 应用更容易碰到 OutOfMemory 的问题(永久代有-XX:MaxPermSize
的上限,即使没有设置也有默认大小)。
在 JDK 6 时候,HotSpot 就已经提出了放弃永久代,逐步改用本地内存(Native Memory)来实现方法区的计划了(JEP 122)。 到了 JDK 7,已经把原本放在永久代的字符串常量池与静态变量等移出,而到了 JDK 8,终于完全废弃了永久代的概念,在本地内存中实现的元空间(Metaspace)来代替。
运行时常量池
- 运行时常量池是方法区的一部分;
- 存放 Class 文件中的常量(final static 修饰的 ConstantValue),编译期产生;
- 存放运行时产生的常量,比如
Strin.intern()
方法; - 内存方面受到方法区的内存限制,内存不足时抛出 OutOfMemoryError 异常。
直接内存
直接内存(Direct Memory)不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域。
- 在使用 NIO 时,通过 Native 函数库直接分配堆外内存,然后通过存储在 Java 堆中的
DirectByteBuffer
对象操作这块内存; - 直接内存分配不受 Java 堆大小的限制,但受到机器总内存的限制;
- 当无法分配到内存时会抛出 OutOfMemoryError 异常。
文章评论