点击上方“IT那活儿”公众号--专注于企业全栈运维技术分享,不管IT什么活儿,干就完了!!! Java虚拟机(JVM)在执行Java程序时,会将其管理的内存划分为多个不同的数据区域。这些区域各有其特定的用途和特性,理解这些内存区域对于深入掌握Java性能优化和故障排查至关重要。 本文将换一种阐述方式来详细解析JVM内存区域的各个部分。
JVM内存区域概览
JVM内存区域主要包括:
程序计数器(PC寄存器);
Java虚拟机栈(JVM栈);
本地方法栈;
堆;
方法区(在JDK 1.8以后称为元空间);
运行时常量池以及直接内存。
这些区域可以大致分为线程私有和线程共享两类。
线程私有的内存区域
2.1 程序计数器(PC寄存器)
程序计数器是每个线程独有的,用于存储当前线程正在执行的Java方法的JVM指令地址。它是线程私有的,生命周期与线程相同。
由于Java的线程是轮流执行的,程序计数器负责记录每个线程的执行位置,以便线程切换后能恢复到正确的执行点。程序计数器是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
2.2 Java虚拟机栈(JVM栈)
Java虚拟机栈也是线程私有的,生命周期与线程相同。每个线程在创建时都会创建一个虚拟机栈,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
每次方法调用都会创建一个栈帧(Stack Frame)来存储这些信息。栈帧的入栈和出栈过程对应着方法的调用和返回。虚拟机栈的主要异常有StackOverflowError(栈深度超过限制)和OutOfMemoryError(内存扩展失败)。
- 局部变量表
用于存储方法参数和局部变量,在编译期间分配内存空间。 - 操作数栈
用于保存计算过程的中间结果和变量临时存储空间,采用后进先出(LIFO)的栈结构。
2.3 本地方法栈
本地方法栈与Java虚拟机栈类似,但它主要用于执行Native方法(如C/C++编写的方法)。在某些JVM实现中,本地方法栈和Java虚拟机栈是合二为一的。
线程共享的内存区域
3.1 堆(Heap)
堆是JVM中最大的一块内存区域,用于存放对象实例。堆被所有线程共享,是垃圾收集器的主要工作区域。Java堆在逻辑上被划分为新生代(Young Generation)和老年代(Old Generation),新生代又被进一步划分为Eden区和两个Survivor区(From Survivor和To Survivor)。新生代的对象在经历多次垃圾收集后仍然存活,将被转移到老年代。堆内存不足时会抛出OutOfMemoryError异常。
3.2 方法区(元空间)
在JDK 1.8及以后的版本中,方法区被元空间(Metaspace)所取代。方法区(或元空间)用于存储已被虚拟机加载的类信息、常量、静态变量等数据。虽然方法区被描述为堆的逻辑部分,但它有“非堆”(Non-Heap)的别名,以区别于Java堆。方法区可以选择不实现垃圾收集,但在内存不足时同样会抛出OutOfMemoryError异常。
3.3 运行时常量池
运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。运行时常量池具有动态性,即不仅限于存放编译期产生的常量,还可以在运行时将新的常量放入池中。当运行时常量池无法申请到足够内存时,也会抛出OutOfMemoryError异常。
直接内存
直接内存不属于JVM运行时数据区的一部分,但它是通过NIO(New Input/Output)类引入的一种堆外内存。直接内存可以显著提高I/O性能,因为它避免了在Java堆和Native堆之间复制数据。然而,直接内存的使用受到本机总内存的限制,若分配不当,可能导致OutOfMemoryError异常。
JVM内存区域的划分和管理是Java性能优化的关键。
了解每个内存区域的作用、特性和异常类型,有助于我们更好地编写高效、稳定的Java程序。

本文作者:李伟康(上海新炬中北团队)
本文来源:“IT那活儿”公众号
