JVM 概述
JVM 内存模型
程序计数器
是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。
每个线程都有一个独立的程序计数器,用于线程切换后能恢复到正确的执行位置。
虚拟机栈
- 每个线程都有自己的虚拟机栈,用于存储方法的局部变量表、操作数栈、动态链接、方法出口等信息。
- 当线程调用一个方法时,会为该方法创建一个栈帧并压入虚拟机栈;方法执行完毕后,栈帧出栈。
本地方法栈
- 与虚拟机栈的作用类似,只不过本地方法栈是为执行 Native 方法服务的。
- 用于存储本地方法(使用 JNI 编写的方法)的调用信息。
堆
- 被所有线程共享的一块内存区域,用于存放对象实例和数组。
- 是垃圾收集器管理的主要区域。
方法区
- 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 在 Java 8 之后,方法区被元空间(Metaspace)替代。
运行时常量池
- 是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。
JVM 垃圾回收
Java 内存模型
Java 内存模型(JMM)时 Java 语言规范中定义的一种内存模型,它规定了 Java 程序中各种变量(线程共享变量)的访问规则,以及在并发环境下如何保证这些变量的内存一致性。
JMM 是 Java 并发编程的基础,它确保了在多线程环境中,不同线程对共享变量的读写操作能够按照预期的顺序执行。
JMM 的主要目标:
- 保证原子性:确保单个操作(如读取、赋值)不可分割,要么完全执行,要么完全不执行。
- 保证可见性:确保当一个线程修改了共享变量的值,其他线程能够立即看到这个修改。
- 保证有序性:确保程序中的操作按照程序员预期的顺序执行,即使在多线程环境下也是如此。
JMM 中的相关概念:
- 主内存:所有线程共享的内存区域,用于存储共享变量。
- 工作内存:每个线程自己的内存区域,存储了主内存中共享变量的副本。
- happens-before 规则:一种偏序关系,用于确定两个操作的执行顺序。如果一个操作happens-before另一个操作,那么第一个操作的结果对第二个操作必须是可见的。
- 内存屏障:一种同步机制,用于控制指令的执行顺序。
- volatile:当一个变量被声明为volatile时,JVM会在对该变量的读写操作前后自动插入内存屏障,以确保变量的读写操作不会被重排序,并且对所有线程都是可见的。
- synchronized:当一个方法或代码块被声明为synchronized时,JVM会在进入和退出同步块时自动插入内存屏障,以确保在同步块内的写操作对其他线程可见,并且在同步块执行期间,其他线程不能进入该同步块。
- Locks:在使用显式锁(如ReentrantLock)时,JVM也会在加锁和解锁操作中插入内存屏障,以确保锁的可见性和内存一致性。
- Atomic 操作:Java并发包中的原子类(如AtomicInteger、AtomicReference等)使用底层的硬件指令和内存屏障来保证操作的原子性和可见性。
- volatile 关键字:用于声明一个变量的读写操作都直接作用于主内存,而不是工作内存。
- synchronized 关键字:用于声明一个方法或代码块的执行需要获取锁,确保同一时刻只有一个线程可以执行该段代码。
JVM 中的锁升级
- 无锁状态
- 初始状态下,对象没有被锁定,任何线程都可以通过CAS(Compare-And-Swap)操作来尝试获取锁。
- 偏向锁状态
- 当线程首次访问同步块时,JVM 可能会将对象偏向该线程,即把对象头中的锁标记设置为偏向模式,并存储当前线程的ID。如果同一线程再次访问,无需进行同步操作。
- 轻量级锁状态
- 如果有另一个线程尝试获取偏向锁,JVM会撤销偏向状态,将锁升级为轻量级锁。轻量级锁通过CAS操作来实现,如果竞争不激烈,这种锁的开销很小。
- 重量级锁状态
- 当轻量级锁状态下发生线程竞争,即有多个线程尝试获取同一把锁时,JVM会将锁进一步升级为重量级锁。重量级锁依赖操作系统的互斥量实现,开销较大,但可以处理更复杂的同步场景。