外观
JVM 内存结构是什么
根据 JDK 8
规范,JVM
运行时内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。还有一部分内存叫直接内存,属于操作系统的本地内存,也是可以直接操作的。
程序计数器
- 定义:是
JVM
中的一块小型内存区域,专门用于存储当前线程正在执行的字节码指令的地址。若当前线程正在执行的是本地方法,此时程序计数器的值将为undefined
,表示没有指向任何有效的字节码指令。 - 作用:一方面作为字节码解释器的核心组件,通过指向当前执行的字节码指令,确保程序能够顺序地读取并执行指令,实现控制流的管理;另一方面在多线程环境中,为每个线程提供独立的执行位置记录,使线程被切换时,能准确恢复到上次执行的状态,确保程序的连续性和一致性。
- 特点:占用内存空间相对较小,通常只有几百字节,能快速访问,减少对系统资源的消耗;每个线程都有自己独立的程序计数器,相互之间不干扰,具有线程私有性;生命周期与线程密切相关,在线程创建时被分配,在线程结束时被销毁,是
JVM
中唯一一个不会引发OutOfMemoryError
的内存区域。
Java
虚拟机栈
- 定义:是
JVM
中用于描述Java
方法运行过程的内存模型,每当一个Java
方法被调用时,JVM
会为其创建一个称为 “栈帧” 的区域,以存储该方法执行过程中的相关信息。 - 结构:由多个栈帧组成,每个栈帧对应一次方法调用,包含局部变量表、操作数栈、动态链接、方法返回地址等信息。每个线程在运行时都有自己的虚拟机栈。
- 生命周期:方法调用时,
JVM
为方法创建新栈帧并压入当前线程的调用栈,局部变量表和操作数栈被初始化;方法执行时,局部变量和操作数在栈帧中操作,计算结果存于操作数栈;方法返回时,JVM
将返回值放入操作数栈,根据返回地址将控制权转回到调用位置,当前栈帧被弹出,释放内存。 - 特点:运行速度非常快,仅次于程序计数器;局部变量表在栈帧创建时分配,大小在编译时确定,运行过程中不会改变;可能会抛出
StackOverflowError
(栈深度超过最大限制)和OutOfMemoryError
(栈允许动态扩展但内存用尽)异常。
本地方法栈
- 定义:用于支持
Java
程序调用本地方法,本地方法是用其他语言编写的方法。 - 作用:为每个线程提供一个独立的栈空间,用于存储本地方法的调用信息和执行状态,与
Java
虚拟机栈类似,但专门用于处理本地方法的调用。 - 结构:与
Java
虚拟机栈相似,主要包括栈帧、局部变量表、操作数栈、返回地址等部分。 - 生命周期:当线程调用本地方法时,
JVM
为该方法创建一个新的栈帧并将其压入本地方法栈中,执行完毕后,栈帧被弹出,控制权返回到调用该方法的位置。
堆
- 定义:是
JVM
内存中最大的一块,被所有线程共享,用于存放由new
创建的对象和数组。 - 分区:分为年轻代和老年代。年轻代又分为
Eden
区和两个幸存者区(Survivor
区,即From
区和To
区)。通常默认配置下,Eden
区、From
区、To
区的内存比例为8:1:1
。 - 特点:对象刚创建时一般存放在
Eden
区,Eden
区内存满时,执行Minor GC
,存活的对象被移动到其中一个Survivor
区,在Survivor
区中经过多次GC
后仍存活的对象会被移动到老年代,大对象可能直接进入老年代。老年代Full GC
清理整个堆空间,发生频率低于Minor GC
,但耗时更长。
方法区
- 定义:被所有线程共享,用于存放静态变量、常量、类信息(版本、方法、字段等)、常量池等,可以看做是将类(
Class
)的元数据保存在方法区里。 - 特点:在
Java 8
中,永久代被元空间取代,元数据区用于存储类信息和类中的方法,不受垃圾收集器管理。