面试题:JVM 的内存区域是如何划分的?

VM(Java虚拟机)的内存区域主要划分为以下几个部分,每个部分都有其特定的用途和生命周期:

  1. 程序计数器(Program Counter Register)
    • 每个线程都有一个独立的程序计数器,用于记录当前线程执行到的方法内部字节码指令的位置。如果当前线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址;如果是Native方法,则计数器值为空(Undefined)。
      这是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
  2. Java虚拟机栈(Java Virtual Machine Stacks)
    • 线程私有的,生命周期与线程相同。每个方法被执行时都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
      每一个方法从调用直至完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。可能出现StackOverflowError(栈深度大于JVM允许的最大深度)或OutOfMemoryError(栈扩展失败)。
  3. 本地方法栈(Native Method Stacks)
    • 类似于Java虚拟机栈,但它为执行本地方法(Native Methods)服务。不同的JVM实现可能使用不同的方式来处理本地方法栈,例如Sun HotSpot直接将本地方法栈和Java虚拟机栈合并处理。
  4. 堆(Heap)
    • 所有线程共享的一块内存区域,是垃圾收集器管理的主要区域,几乎所有的对象实例以及数组都要在堆上分配。堆可以进一步细分为新生代(Eden空间、两个Survivor空间)和老年代。堆空间不足会导致OutOfMemoryError: Java heap space
  5. 方法区(Method Area)
    • 同样是所有线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
      在JDK 8之前称为“永久代”(Permanent Generation),JDK 8及之后版本则移除了永久代的概念,转而使用元空间(Metaspace),并将其放在了本地内存中。
      当方法区无法满足新的内存分配需求时,也会抛出OutOfMemoryError
  6. 运行时常量池(Runtime Constant Pool)
    • 属于方法区的一部分,用于存放编译期生成的各种字面量和符号引用,并且可以在运行期间将新的常量加入到常量池中,如String类的intern()方法。它是Class文件中常量池结构在运行时的表现形式。
  7. 直接内存(Direct Buffer Memory/Off-Heap Memory)
    • 虽然不属于JVM规范定义的标准内存区域,但通过java.nio.ByteBuffer.allocateDirect()申请的直接内存也是JVM内存模型中的一个重要组成部分。
      它不会占用JVM堆的空间,而是直接向操作系统申请内存,适用于需要大量I/O操作的场景。直接内存不足会引发OutOfMemoryError: Direct buffer memory

理解这些内存区域的作用和特性对于编写高效且无误的Java程序至关重要,特别是在考虑性能优化、内存管理和异常处理等方面。

THE END
喜欢就支持一下吧
点赞13 分享