Java 虚拟机(JVM)在运行时将内存划分为多个区域,以便有效管理和分配内存资源。这些内存区域包括堆(Heap)、方法区(Method Area)、程序计数器(Program Counter Register)、虚拟机栈(JVM Stack)和本地方法栈(Native Method Stack)。每个区域的作用和管理方式有所不同,某些区域在特定情况下可能会发生 OutOfMemoryError
。
JVM 内存区域划分
-
堆(Heap):
- 用途:用于存储对象实例和数组,是垃圾回收的主要区域。
- 特点:堆内存是所有线程共享的。
- 子区域:通常分为新生代(Young Generation)和老年代(Old Generation)。新生代又细分为 Eden 区和两个 Survivor 区。
- 可能发生的错误 :
OutOfMemoryError: Java heap space
。当 JVM 无法在堆中分配内存并且垃圾回收也无法回收足够的内存时,会抛出这个错误。
-
方法区(Method Area)(在 JDK 8 之前称为永久代(Permanent Generation),JDK 8 及以后称为元空间(Metaspace)):
- 用途:用于存储类的元数据、常量池、静态变量和即时编译器(JIT)编译后的代码。
- 特点:方法区是所有线程共享的。在 JDK 8 之前,方法区是堆的一部分;在 JDK 8 及以后,方法区从堆中移出,成为一个独立的区域。
- 可能发生的错误 :
- JDK 8 之前:
OutOfMemoryError: PermGen space
。当永久代无法为类元数据分配内存时会抛出此错误。 - JDK 8 及以后:
OutOfMemoryError: Metaspace
。当元空间无法分配足够的内存时会抛出此错误。
- JDK 8 之前:
-
程序计数器(Program Counter Register):
- 用途:每个线程都有一个独立的程序计数器,用于记录当前线程所执行的字节码的行号指示器。在执行本地方法时,程序计数器为空。
- 特点:程序计数器是线程私有的,不会发生内存溢出。
-
虚拟机栈(JVM Stack):
- 用途:用于存储每个方法执行的局部变量表、操作数栈、动态链接、方法返回地址等信息。每个方法调用都会创建一个栈帧。
- 特点:虚拟机栈是线程私有的。
- 可能发生的错误 :
StackOverflowError
:当线程请求的栈深度超出虚拟机栈的最大深度时会抛出此错误。OutOfMemoryError
:当 JVM 无法申请到足够的内存来扩展虚拟机栈时会抛出此错误。
-
本地方法栈(Native Method Stack):
- 用途:与虚拟机栈类似,但用于存储本地方法调用的栈帧。
- 特点:本地方法栈是线程私有的。
- 可能发生的错误 :
StackOverflowError
:当线程请求的栈深度超出本地方法栈的最大深度时会抛出此错误。OutOfMemoryError
:当 JVM 无法申请到足够的内存来扩展本地方法栈时会抛出此错误。
总结
JVM 内存区域的划分有助于管理不同类型的数据和信息,并优化垃圾回收和内存分配。以下是各区域可能发生的 OutOfMemoryError
:
- 堆(Heap) :
OutOfMemoryError: Java heap space
- 方法区(Method Area) :
- JDK 8 之前:
OutOfMemoryError: PermGen space
- JDK 8 及以后:
OutOfMemoryError: Metaspace
- JDK 8 之前:
- 虚拟机栈(JVM Stack) :
StackOverflowError
、OutOfMemoryError
- 本地方法栈(Native Method Stack) :
StackOverflowError
、OutOfMemoryError
需要注意的是,程序计数器是不会发生内存溢出的。
理解 JVM 内存区域的划分及其可能发生的错误,有助于开发人员更好地调优应用程序,避免和处理内存相关的问题。