Skip to content

Latest commit

 

History

History
89 lines (45 loc) · 5.1 KB

jvm_memory_areas.md

File metadata and controls

89 lines (45 loc) · 5.1 KB

JVM 内存区域

JVM-03-1

图:JVM 内存区域知识图谱

JVM 内存区域主要分为:

  • 线程私有区域「程序计数器、虚拟机栈、本地方法区」

  • 线程共享区域「Java 堆、方法区」

  • 直接内存

JVM-03-1

图:JVM 内存区域

线程私有数据区域生命周期与线程相同,依赖用户线程的创建与消亡。在 HotSpot VM 内,每个线程都与操作系统的本地线程直接映射,因此这部分内存区域跟随本地线程创建消亡。

线程共享区域跟随 JVM 虚拟机实例的启停而创建消亡。

直接内存又称堆外内存,并不是 JVM 运行时数据区的一部分,但也会被频繁使用到(如:JDK 1.4 引入NIO特性)。

程序计数器

一块较小的内存空间,存储当前线程所执行的字节码的行号指示器。每条线程都要有一个独立的程序计数器,执行 Java 方法时,程序计数器记录虚拟机字节码指令地址(当前指令的地址),执行 Native 方法时则为空。

程序计数器内存区域是唯一一个在 JVM 虚拟机规范中没有规定任何OutOfMemoryError情况的内存区域。

虚拟机栈

每个线程有一个私有的栈,随着线程的创建而创建。栈中存放栈帧(Stack Frame),每个方法在执行的时候会创建一个栈帧,存储了局部变量表(基本数据类型与对象引用)、操作数栈、动态连接、方法出口等信息。每个方法从调用到执行完毕,对应一个栈帧在虚拟机栈中的入栈和出栈。

通常所说的栈,一般是指虚拟机栈中的局部变量表部分。局部变量表所需的内存在编译期间完成分配。栈的大小可以固定也可以动态扩展,当扩展到无法申请足够的内存,则抛出OutOfMemoryError。当栈调用深度大于 JVM 所允许的范围,会抛出StackOverflowError错误,不过这个栈深度范围并不是一个恒定的值,可以经由 JVM 启动参数进行调整。

JVM-03-3

图:JVM 运行时栈帧结构

本地方法区

本地方法区和 Java 栈的作用类似,区别是虚拟机栈为执行 Java 方法服务,而本地方法栈则为执行 Native 方法服务。如果一个 JVM 实现使用 C-linkage 模型来支持 Native 调用,那么该栈将会是一个 C 栈。在 HotSpot VM 中,直接将本地方法栈和虚拟机栈合二为一了。

堆(Heap)是线程共享的一块内存区域,Java 程序中创建的实例对象和数组都保存在 Java 堆中。Java 堆也是垃圾收集器进行垃圾收集最主要的内存区域。

由于现代 Java 虚拟机基本都采用分代垃圾收集算法,因此堆区从 GC 的角度还可以细分为:

  • 新生代(Eden 区、From Survivor 区、To Survivor 区)

  • 老年代

JVM-03-4

图:JVM 运行时堆结构

永久代方法区

即我们常说的永久代(Permanent Generation),用于存储被 JVM 加载的「类信息、常量、静态变量、即时编译器编译后的代码」等数据。HotSpot VM 把 GC 分代收集扩展至永久代方法区,使用 Java 堆的永久代垃圾收集器来实现方法区,这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存,而不必为方法区开发专门的内存管理器。永久代内存回收的主要目标是针对常量池的回收和无用类型的卸载。

GC 不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常。

运行时常量池(Runtime Constant Pool)是方法区的一部分。.class文件中除了有类的版本、字段、方法、接口等信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用。这部分数据将在类加载后存放到方法区的运行时常量池中。JVM 虚拟机对.class文件的每一部分(也包括常量池)格式都有严格的规定,每一个字节用于存储哪种数据都必须符合虚拟机规范上的要求,才能被虚拟机认可、装载、执行。

在 JDK 1.7 中,已经把放在永久代的「字符串常量池」转移至 JVM 堆。

Java 8 与元数据区

在 Java 8 中,永久代已经移除,被一个称为「元数据区」(元空间)的内存区域取代。

JVM-03-5

图:Java 8 内存区域变化

元数据区(Metaspace)与永久代(PermGen)类似,它们之间的区别在于:元数据区并不在虚拟机内存区域内,而是使用本地内存。因此在默认情况下,元数据区的大小仅受本地内存限制。.class类的元数据放入本地内存(Native Memory),字符串池和类静态变量放入 Java 堆中,这样一来可以加载多少类的元数据就不再由MaxPermSize控制,而由系统实际可用内存空间控制。