JVM 概述

JVM 内存模型

  1. 程序计数器

    • 是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。

    • 每个线程都有一个独立的程序计数器,用于线程切换后能恢复到正确的执行位置。

  2. 虚拟机栈

    • 每个线程都有自己的虚拟机栈,用于存储方法的局部变量表、操作数栈、动态链接、方法出口等信息。
    • 当线程调用一个方法时,会为该方法创建一个栈帧并压入虚拟机栈;方法执行完毕后,栈帧出栈。
  3. 本地方法栈

    • 与虚拟机栈的作用类似,只不过本地方法栈是为执行 Native 方法服务的。
    • 用于存储本地方法(使用 JNI 编写的方法)的调用信息。
    • 被所有线程共享的一块内存区域,用于存放对象实例和数组。
    • 是垃圾收集器管理的主要区域。
  4. 方法区

    • 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
    • 在 Java 8 之后,方法区被元空间(Metaspace)替代。
  5. 运行时常量池

    • 是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。

JVM 垃圾回收

Java 内存模型

Java 内存模型(JMM)时 Java 语言规范中定义的一种内存模型,它规定了 Java 程序中各种变量(线程共享变量)的访问规则,以及在并发环境下如何保证这些变量的内存一致性。

JMM 是 Java 并发编程的基础,它确保了在多线程环境中,不同线程对共享变量的读写操作能够按照预期的顺序执行。

JMM 的主要目标:

  1. 保证原子性:确保单个操作(如读取、赋值)不可分割,要么完全执行,要么完全不执行。
  2. 保证可见性:确保当一个线程修改了共享变量的值,其他线程能够立即看到这个修改。
  3. 保证有序性:确保程序中的操作按照程序员预期的顺序执行,即使在多线程环境下也是如此。

JMM 中的相关概念:

  • 主内存:所有线程共享的内存区域,用于存储共享变量。
  • 工作内存:每个线程自己的内存区域,存储了主内存中共享变量的副本。
  • happens-before 规则:一种偏序关系,用于确定两个操作的执行顺序。如果一个操作happens-before另一个操作,那么第一个操作的结果对第二个操作必须是可见的。
  • 内存屏障:一种同步机制,用于控制指令的执行顺序。
    • volatile:当一个变量被声明为volatile时,JVM会在对该变量的读写操作前后自动插入内存屏障,以确保变量的读写操作不会被重排序,并且对所有线程都是可见的。
    • synchronized:当一个方法或代码块被声明为synchronized时,JVM会在进入和退出同步块时自动插入内存屏障,以确保在同步块内的写操作对其他线程可见,并且在同步块执行期间,其他线程不能进入该同步块。
    • Locks:在使用显式锁(如ReentrantLock)时,JVM也会在加锁和解锁操作中插入内存屏障,以确保锁的可见性和内存一致性。
    • Atomic 操作:Java并发包中的原子类(如AtomicInteger、AtomicReference等)使用底层的硬件指令和内存屏障来保证操作的原子性和可见性。
  • volatile 关键字:用于声明一个变量的读写操作都直接作用于主内存,而不是工作内存。
  • synchronized 关键字:用于声明一个方法或代码块的执行需要获取锁,确保同一时刻只有一个线程可以执行该段代码。

JVM 中的锁升级

  1. 无锁状态
    • 初始状态下,对象没有被锁定,任何线程都可以通过CAS(Compare-And-Swap)操作来尝试获取锁。
  2. 偏向锁状态
    • 当线程首次访问同步块时,JVM 可能会将对象偏向该线程,即把对象头中的锁标记设置为偏向模式,并存储当前线程的ID。如果同一线程再次访问,无需进行同步操作。
  3. 轻量级锁状态
    • 如果有另一个线程尝试获取偏向锁,JVM会撤销偏向状态,将锁升级为轻量级锁。轻量级锁通过CAS操作来实现,如果竞争不激烈,这种锁的开销很小。
  4. 重量级锁状态
    • 当轻量级锁状态下发生线程竞争,即有多个线程尝试获取同一把锁时,JVM会将锁进一步升级为重量级锁。重量级锁依赖操作系统的互斥量实现,开销较大,但可以处理更复杂的同步场景。