【Java】JVM内存结构

JVM内存结构

JVM(Java Virtual Machine,Java 虚拟机)是运行 Java 程序的核心组件,是 Java 的运行时环境,它将 Java 编译后的字节码 .class 文件,转换为对应平台的机器指令并执行


一句话定义

JVM 是一种虚拟计算机,负责加载、验证、解释/编译和执行 Java 字节码,并管理程序的内存、线程、垃圾回收等运行细节。


JVM 的作用

功能 描述
跨平台执行 一次编写,到处运行(WORA)
自动内存管理 包括堆内存分配、垃圾回收(GC)等
安全性保障 字节码验证、类加载机制防止恶意代码
线程调度与管理 提供并发编程支持(线程栈、线程调度器等)
性能优化 JIT 编译器可将热点代码转为本地机器码执行,提升运行效率

JVM 的工作流程图(简化)

Java源代码(.java)
        ↓  编译
Java编译器 (javac)
        ↓  输出字节码
字节码文件(.class)
        ↓  加载执行
JVM(类加载器 + 执行引擎 + 内存管理 + 垃圾回收)
        ↓
平台相关的机器码 → 输出结果

JVM 内存结构(运行时数据区)

区域 描述
方法区(元空间) 类结构、常量池、静态变量、运行时常量等信息
堆(Heap) 所有对象实例的内存区域,是 GC 的主要区域
栈(Stack) 每个线程私有,保存局部变量、方法调用栈帧
程序计数器 记录当前线程执行的字节码行号(指令地址)
本地方法栈 调用 native 本地方法时使用

JVM 执行引擎

  • 解释器:逐条解释执行字节码(启动快)
  • JIT 编译器:将热点代码编译为机器码(运行快)

JVM 与 JDK、JRE 的关系

        JDK
     ┌──────────┐
     │ JRE      │ 包含 JVM + Java 核心类库
     │ ┌──────┐ │
     │ │ JVM  │ │ ← 执行 Java 程序的核心
     │ └──────┘ │
     └──────────┘
  • JDK:开发工具包(包含 JRE)
  • JRE:运行环境(包含 JVM 和类库)
  • JVM:运行程序的虚拟机引擎

总结

项目 JVM 的作用
运行环境 加载和执行 Java 字节码
平台无关 一次编写,到处运行
内存管理 管理对象分配和垃圾回收
安全高效 字节码验证 + JIT 优化

Java内存模型(JMM)

Java 内存模型(Java Memory Model,JMM)定义了 Java 虚拟机中多个线程之间如何共享变量,以及 在并发环境下如何保证可见性、有序性和原子性


为什么需要 Java 内存模型(JMM)

多线程编程中的三个核心问题:

问题类型 含义
原子性 操作不可中断,例如 i++ 不是原子操作
可见性 一个线程对共享变量的修改,其他线程能否立即看到
有序性 指令可能会被 CPU 或编译器重排序,影响预期执行顺序

JMM 就是为了解决这些问题,让 Java 并发编程更可控、更安全。


Java 内存模型结构图

┌─────────────────────────────────────────────┐
│                  主内存 (Main Memory)        │ ← 所有线程共享变量存储地
│ ┌────────┐ ┌────────┐ ┌────────┐             │
│ │ var A  │ │ var B  │ │ var C  │ ← 所有共享变量 │
│ └────────┘ └────────┘ └────────┘             │
└─────▲───────────────────────────────────────┘
      │
┌─────┴────┐     ┌─────┴────┐
│ 线程1    │     │ 线程2    │ ← 每个线程有独立工作内存
│ ┌──────┐ │     │ ┌──────┐ │
│ │ A'   │ │     │ │ A''  │ │ ← 拷贝的变量副本
│ └──────┘ │     │ └──────┘ │
└─────────┘     └─────────┘

JMM 的三大特性

特性 描述
原子性 基本数据类型的读写是原子的,但复合操作(如 i++)不是
可见性 一个线程修改了主内存,其他线程能看到。用 volatile、锁来保证
有序性 JMM 允许指令重排序,但会通过 happens-before 原则保证最终结果正确

volatile 的作用(JMM 的典型应用)

  1. 保证可见性:修改立即刷新到主内存
  2. 禁止指令重排序:加了内存屏障,防止重排
  3. 不保证原子性:不能替代加锁

happens-before(先行发生)原则

JMM 通过“happens-before”规则规定了操作间的顺序性:

规则 描述
程序顺序规则 单线程内代码的执行顺序
监视器锁规则(synchronized) 解锁 happens-before 同一锁后加锁
volatile 变量规则 写 volatile 变量先于之后的读
线程启动规则(Thread.start) start 之前的操作先行发生于线程内部代码
线程终止规则(Thread.join) 主线程等待子线程结束
传递性规则 A → B 且 B → C,则 A → C

JVM 内存结构 vs JMM(对比)

JVM 内存结构 Java 内存模型(JMM)
方法区、堆、栈 主内存、工作内存(线程私有)
控制程序运行时内存布局 控制变量如何在线程之间同步
面向运行时 面向并发语义

总结一句话

Java 内存模型(JMM)是并发编程的核心基础,确保多线程下数据共享时的原子性、可见性、有序性,通过 volatilesynchronizedfinal 等关键字落地实现。


JMM三大特性

Java 内存模型(JMM)中的 原子性、可见性、有序性 是并发编程的三大核心保障。JMM 通过一系列语言规范、关键字和底层硬件机制来实现它们。下面是三者的详解及其实现方式:


1. 原子性(Atomicity)

定义:

一个操作不可被中断,即使多个线程同时执行,也不会出现数据不一致。

JMM 如何实现:

操作类型 是否原子 说明
基本类型的读写 int a = 1; 是原子的
复合操作如 i++ 实际为读 → 改 → 写,非原子操作

保障原子性的手段

  • 使用 **synchronized** 关键字加锁
  • 使用 **ReentrantLock**
  • 使用原子类如 **AtomicInteger**(基于 CAS)

2. 可见性(Visibility)

定义:

当一个线程修改了共享变量,其他线程能够立即看到该值的变化

问题背景:

由于每个线程有自己的 工作内存(变量副本),修改操作可能不会立刻同步到主内存。

JMM 如何实现:

  • volatile:对变量的读写会直接从主内存进行,写操作立即刷新到主内存,读操作强制从主内存读取。
  • synchronized/lock:加锁时会清空工作内存,从主内存读取最新值;释放锁时会刷新工作内存到主内存。
  • final:构造器中正确构造的 final 字段,对其他线程是可见的。

3. 有序性(Ordering)

定义:

代码的执行顺序和书写顺序一致。

问题背景:

为了提高性能,编译器/CPU/JVM 可能会对指令进行重排序。

JMM 如何实现:

  • happens-before 规则(核心规则,保障有序性)

    • 同一个线程内,前面的操作先于后面的操作
    • 对同一个锁的 unlock 先于 lock
    • volatile 写先于读
    • start() 先于子线程代码执行
  • volatile:插入内存屏障,禁止重排序

  • synchronized:锁的释放/获取建立内存屏障和顺序


内存屏障(Memory Barrier)

底层 CPU 支持的指令屏障,用来:

  • 保证特定指令的顺序执行
  • 刷新/读取内存,保障可见性和有序性

Java 的 volatile 会在读写操作前后插入内存屏障。


三者的对比总结

特性 问题解决 关键字 / 工具 是否 CPU 保证
原子性 操作不可中断 synchronized、AtomicXXX、Lock 部分
可见性 变量对线程共享 volatile、synchronized、final 否(通过缓存)
有序性 指令顺序正确 volatile、synchronized、happens-before 否(需保障)

示例代码演示(可见性问题)

class VisibilityDemo {
    static boolean flag = false;

    public static void main(String[] args) {
        new Thread(() -> {
            while (!flag) {
                // do something
            }
            System.out.println("Flag is true");
        }).start();

        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        flag = true; // 主线程修改,子线程可能看不到(没有可见性)
    }
}

若将 flag 改为 volatile boolean flag,则保证子线程可见。


三大特性针对的数据

主存中存储哪些变量

类型 是否存储在主内存中? 原因说明
成员变量 ✅ 是 属于对象,在堆中,对象之间共享,需要线程同步
静态变量 ✅ 是 属于类元数据,在方法区中,也是共享变量
数组元素 ✅ 是 数组本质上是对象,对象存在主内存(堆)中
final变量 ✅ 是(构造后不可变) 被正确初始化后可以被其他线程安全共享
局部变量(栈帧) ❌ 否 每个线程独立栈帧,只存在于线程私有的栈中
方法参数 ❌ 否 和局部变量一样,在栈中,是线程私有的

你可能感兴趣的:(Java,java,jvm,开发语言)