1. JVM 内存区域 学习笔记

1.1. JVM 内存区域

JVM将内存划分为不同区域主要是为了高效管理、安全隔离和性能优化,这种设计是经过长期实践验证的架构选择。以下是内存区域划分的核心原因:

  1. 职责分离原则

    • 不同数据有不同的生命周期和使用方式(如类信息与对象实例)
    • 分离存储便于针对性管理(如GC策略不同)
  2. 性能优化

    • 热点数据单独存放(如栈上的局部变量快速访问)
    • 减少内存碎片(堆与栈使用不同分配策略)
  3. 安全隔离

    • 防止用户代码直接操作方法区等关键区域
    • 线程私有数据避免并发问题(如程序计数器)

JVM(Java虚拟机)内存区域是Java程序运行时数据存储的关键部分,了解这些区域对于性能调优和故障排查至关重要。JVM内存主要分为以下几个区域(方法区、程序计数器、Java虚拟机栈、Java堆内存):

1.1.1. 方法区(Method Area)

该区域主要 存放 从“.class”文件里加载进来的类。(包括一些常量池等数据)

总结

  • 线程共享:存储已被虚拟机加载的类型信息
  • 内容
    • 类型信息(类名、访问修饰符等)
    • 常量池(Runtime Constant Pool)
    • 字段描述
    • 方法描述
    • 方法字节码
    • 类静态变量
  • 实现变化
    • JDK 7及之前:永久代(PermGen)
    • JDK 8+:元空间(Metaspace,使用本地内存)
  • 异常:OutOfMemoryError

1.1.2. 运行时常量池(Runtime Constant Pool)

总结

  • 位置:方法区的一部分
  • 内容
    • 类文件常量池的运行时表示
    • 符号引用(Symbolic References)
    • 字面量(Literals)
  • 特点
    • 动态性:运行时可以将新的常量放入池中
    • 如String.intern()方法

1.1.3. 程序计数器(Program Counter Register)

计算机不能直接识别代码,如果想让计算机执行代码逻辑,先要把代码转换成字节码(.java -> .class)。

.class文件中就是编译出来的字节码,文件内容就是一条条机器指令(eg:从内存读取某个数据,或者把某数据写到内存等,各种各样指令的集合)。

我们写好的Java代码会被翻译成字节码,对应各种字节码指令。JVM加载类信息到内存之后,会使用字节码执行引擎,去执行我们写的代码编译出来的代码指令。

在执行字节码指令的时候,JVM里就需要一个特殊的内存区域了,那就是“程序计数器”。

程序计数器就是用来记录当前执行的字节码指令的位置的,也就是记录目前执行到了哪一条字节码指令(每个线程都有自己的程序计数器)。

总结

  • 线程私有:每个线程都有独立的程序计数器
  • 作用:记录当前线程执行的字节码指令地址
  • 特点
    • 执行Java方法时记录虚拟机字节码指令地址
    • 执行Native方法时值为空(Undefined)
    • 唯一不会出现OutOfMemoryError的内存区域

1.1.4. Java虚拟机栈(Java Virtual Machine Stacks)

Java代码在执行的时候,一定是线程来执行某个方法中的代码。在方法里,我们经常会定义一些方法内的局部变量。

因此,JVM必须有一块区域是来保存每个方法内的局部变量等数据,这个区域就是Java虚拟机栈。(每个线程都有自己的Java虚拟机栈

如果线程执行了一个方法,就会对这个方法调用创建对应的一个栈帧。栈帧里就有这个方法的局部变量表 、操作数栈、动态链接、方法出口等。

使用下列代码和画图,方便理解记忆:

1. 执行main()方法

public class Kafka {
    public static void main(String[] args) {
        ReplicaManager replicaManager = new ReplicaManager();
        replicaManager.loadReplicasFromDisk();
    }
}

在执行上面代码的时候,就会通过main线程对应的程序计数器记录自己执行的指令位置。该线程会有自己的 Java 虚拟机栈,用来保存执行的方法及对应的局部变量replicaManager

jvm_stack

2. 当main()方法继续执行ReplicaManager对象里面的方法时

public class ReplicaManager {
    public void loadReplicasFromDisk() {
        Boolean hasFinishesLoad = false;
        if (isLocalDataCorrupt()) {
            System.out.println("执行指定逻辑。。。");
        }
    }

    public boolean isLocalDataCorrupt() {
        boolean isCorrupt = false;
        return isCorrupt;
    }
}

当执行到loadReplicasFromDisk()方法时,会把该方法及对应局部变量等,压入虚拟机栈中。

jvm_stack

继续执行代码,发现又调用了isLocalDataCorrupt()方法,还是继续上述操作,将该方法及局部变量等压入虚拟机栈中。

jvm_stack

3. 方法执行完后的操作

isLocalDataCorrupt()方法执行完毕后,会将isLocalDataCorrupt()方法对应的栈帧从 Java 虚拟机栈中出栈。

继续执行loadReplicasFromDisk()方法,该方法执行完毕,将该方法的栈帧出栈。按此步骤直到所有方法都执行完毕。

总结

  • 线程私有:生命周期与线程相同
  • 作用:存储方法调用的栈帧(Stack Frame)
  • 组成
    • 局部变量表:存储方法参数和局部变量
    • 操作数栈:方法执行的工作区
    • 动态链接:指向运行时常量池的方法引用
    • 方法返回地址:方法执行完毕后的返回位置
  • 异常
    • StackOverflowError:线程请求的栈深度超过限制
    • OutOfMemoryError:无法申请到足够内存扩展栈

1.1.5. 本地方法栈(Native Method Stack)

总结

  • 线程私有:与虚拟机栈类似
  • 作用:为Native方法服务
  • 特点
    • HotSpot虚拟机中与虚拟机栈合二为一
    • 同样会抛出StackOverflowError和OutOfMemoryError

1.1.6. Java堆(Java Heap)

新创建对象的数据存储区域Java堆内存。堆内存的数据,线程之间都可以共享访问的

public class Kafka {
    public static void main(String[] args) {
        ReplicaManager replicaManager = new ReplicaManager();
        replicaManager.loadReplicasFromDisk();
    }
}

public class ReplicaManager {
    private long replicaNum;

    public void loadReplicasFromDisk() {
        Boolean hasFinishesLoad = false;
        if (isLocalDataCorrupt()) {
            System.out.println("执行指定逻辑。。。");
        }
    }

    public boolean isLocalDataCorrupt() {
        boolean isCorrupt = false;
        return isCorrupt;
    }
}

上述代码main()方法中new ReplicaManager()会创建一个新的对象实例(包含一些数据,例如replicaNum等)。该对象实例数据会放在 Java堆内存 中。

Java虚拟机栈 的局部变量replicaManager是引用类型,存放ReplicaManager对象的堆内存地址。(相当于局部变量表replicaManager指向堆内存的ReplicaManager对象)

java_heap

总结

  • 线程共享:所有线程共享的内存区域
  • 作用:存储对象实例和数组
  • 特点
    • 虚拟机启动时创建
    • 垃圾收集器管理的主要区域(GC堆)
    • 可以处于物理上不连续的内存空间
  • 分代设计(大多数JVM实现):
    • 新生代(Young Generation):
      • Eden区
      • Survivor区(From/To)
    • 老年代(Old Generation/Tenured)
    • 永久代/元空间(JDK 8+改为元空间)
  • 异常:OutOfMemoryError

1.1.7. 直接内存(Direct Memory)

总结

  • 非JVM规范定义:但被广泛使用
  • 特点
    • 不是JVM运行时数据区的一部分
    • 通过Native函数库直接分配堆外内存
    • 常见于NIO的DirectByteBuffer
  • 优势
    • 减少Java堆和Native堆间的数据复制
    • 提高IO性能
  • 异常:OutOfMemoryError

1.1.8. 内存区域对比表

区域名称 线程共享性 是否GC 异常类型 作用
程序计数器 线程私有 指令地址
虚拟机栈 线程私有 SOE/OOME Java方法调用
本地方法栈 线程私有 SOE/OOME Native方法调用
Java堆 线程共享 OOME 对象实例存储
方法区 线程共享 OOME 类信息、常量池等
运行时常量池 线程共享 OOME 类文件常量池的运行时表示
直接内存 - - OOME 堆外内存

1.2. JVM 核心内存区域全流程图

public class Kafka {
    public static void main(String[] args) {
        ReplicaManager replicaManager = new ReplicaManager();
        replicaManager.loadReplicasFromDisk();
    }
}

public class ReplicaManager {
    private long replicaNum;

    public void loadReplicasFromDisk() {
        Boolean hasFinishesLoad = false;
        if (isLocalDataCorrupt()) {
            System.out.println("执行指定逻辑。。。");
        }
    }

    public boolean isLocalDataCorrupt() {
        boolean isCorrupt = false;
        return isCorrupt;
    }
}

上面代码执行时整体流程如下(附图):

  1. 启动 JVM 进程,会把相关类加载到方法区内存。然后会有 main 线程来执行main()方法。
  2. main 线程关联了一个程序计数器(如果有多个线程执行这段代码,每个线程有自己的程序计数器)。线程执行代码到某个位置,程序计数器都会记录。
  3. 在 main 线程执行程序的时候,会有自己的 Java 虚拟机栈,用来存储正在执行的方法及局部变量。
  4. 其中方法中如果有对象类型,局部变量存的是对象的地址,地址为 Java 堆内存位置(放置的是对象实例)。
  5. 当方法执行完后会从 Java 虚拟机栈中出栈,程序计数器会继续向下执行,直到所有方法执行完,整段代码结束。

jvm_memory_serial_diagram

1.3. 常见内存问题

  1. 堆内存溢出(OOM)

    • java.lang.OutOfMemoryError: Java heap space
    • 原因:对象太多或太大,无法被GC回收
    • 解决:调整-Xmx/-Xms参数,分析内存泄漏
  2. 栈溢出(SOE)

    • 现象:java.lang.StackOverflowError
    • 原因:递归调用过深或栈帧过大
    • 解决:调整-Xss参数,优化递归算法
  3. 元空间溢出

    • 现象:java.lang.OutOfMemoryError: Metaspace
    • 原因:加载类过多(如动态生成类)
    • 解决:调整-XX:MetaspaceSize/-XX:MaxMetaspaceSize
  4. 直接内存溢出

    • 现象:java.lang.OutOfMemoryError: Direct buffer memory
    • 原因:分配过多堆外内存
    • 解决:调整-XX:MaxDirectMemorySize

1.4. JVM内存参数调优

理解JVM内存区域是Java性能调优的基础,合理配置内存参数可以显著提高应用性能和稳定性。

常用参数示例:

java -Xms512m -Xmx1024m -Xss256k -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m ...
  • -Xms:初始堆大小
  • -Xmx:最大堆大小
  • -Xss:线程栈大小
  • -XX:MetaspaceSize:元空间初始大小
  • -XX:MaxMetaspaceSize:元空间最大大小

results matching ""

    No results matching ""