JVM(java虚拟机)

文章目录

JVM------Java虚拟机(Java Virtual Machine)

名称 全称 作用
JDK Java Development Kit Java 开发工具包,包含编译器(javac)、打包工具 (jar)、调试工具(jdb)、JRE 等。是开发和运行 Java 程序的完整环境。
JRE Java Runtime Environment Java 运行环境,包含 JVM + Java 核心类库,让程序可以 运行。
JVM Java Virtual Machine Java 虚拟机,真正负责执行 .class 字节码,并将它翻译为对 应操作系统的机器指令。它在不同系统上有不同实现,从而屏蔽了底层操作系统的差异。

Java 的 一次编写,到处运行 特性:不同操作系统如 Windows、mac、Linux 对应不同的 JDK 版 本,以支持同一份代码编译文件(.class文件)在各种系统中都能运行。实际上,这种跨平台能力主要依靠的是 JDK 中的 JVM(Java 虚拟机) ------它负责在各自的操作系统上,把与平台无关的字节码文件(.class) 翻译成该系统能理解的机器指令。

类的生命周期

加载------>连接------>初始化------>使用------>卸载

  • 加载:查找并加载二进制类的二进制数据(.class文件),即将硬盘上的二进制文件加载到jvm内存中。
  • 连接:确定类与类之间的关系。
    • 验证:.class 正确性校验。
    • 准备:static静态变量分配内存并设置默认初始值(零值变量类型的默认值),不执行显式初始。化。这一阶段,jvm中只有类,没有对象。
    • 解析:将符号引用转换为直接引用,例如把类名、方法名解析为内存地址。如将符号引用com:yanqun.poio:student映射成实际的内存地址,内存地址的引用就是直接引用。
  • 初始化:给静态变量赋真实的初始值。
  • 使用:对象的初始化、对象的垃圾回收、对象的销毁。
  • 卸载:当 类对应的 ClassLoader 被垃圾回收且 该类的所有实例已经被回收,类卸载是 JVM 的可选行为,标准 JVM 并不强制卸载类。

jvm结束生命周期的时机:

  • 正常结束
  • 异常结束/错误
  • System.exit()
  • 操作系统异常

JMM------JVM内存模型(Java Memory Modle)

用于定义变量(所有线程的共享变量,不能是局部变量)的访问规则。

JMM将内存分为两个区:

  • 主内存区:存放真实的变量。
  • 工作内存区:主内存中变量的副本,供各个线程使用。

注:

  • 各个线程只能访问自己私有的工作内存(不能访问其他线程的工作内存,也不能访问主内存)。
  • 不同线程之间,可以通过主内存间接的访问其他线程的工作内存。
    • save:工作内存------>主内存
    • load:主内存------>工作内存

线程之间交互数据的步骤:

  1. Lock(锁定):将主内存中的变量标记为某个线程独占,防止其他线程同时修改。
  2. Read(读取):从主内存读取变量值到线程的工作内存(Working Memory)。
  3. Load(载入):将 Read 读取到的值放入工作内存中的变量副本(local copy)。
  4. Use(使用):将工作内存中的变量副本传递给执行引擎(CPU)使用。
  5. Assign(赋值):将执行引擎得到的值赋给工作内存中的变量副本。
  6. Store(存储):将工作内存中的变量副本写回到主内存,但可能还没生效到最终主内存位置。
  7. Write(写入):将 Store 的值正式写入主内存,使其他线程可见。
  8. Unlock(解锁):解除 Lock,释放主内存变量的独占状态。

JVM要求以上8个动作必须是原子性的

(早期java 1.5之前对于64位的数据类型(long double)有些是非原子性协议,这样就可能出现 半读/半写 的情况。避免方式:可以通过关键字volatile解决。)

volatile和线程安全

【在早期的 Java 版本(1.5 之前),volatile 被用于防止 long 和 double 等 64 位数据类型的非原子性读写问题(即"半读/半写"现象)】

volatile 是 JVM 提供的一种轻量级同步机制,用于

  • 保证多线程环境下变量的可见性
    • 对该变量的写操作会立即刷新到主内存;
    • 对该变量的读操作会从主内存中重新读取最新值;
  • 禁止指令重排序:通过内存屏障来实现的
    • 重排序:编译器或 CPU 为了提高执行效率,在不改变单线程程序最终结果的前提下,调整原子性指令执行顺序的行为。

    • volatile 保证在读写该变量时,程序指令的执行顺序不会被编译器或 CPU 重新排列,
      从而避免多线程下由于指令乱序导致的可见性和安全性问题。

    • 简单示例

      java 复制代码
      int a = 0;
      boolean flag = false;
      
      // 线程1
      a = 1;           // ①
      flag = true;     // ②
      
      // 线程2
      if (flag) {      // ③
          System.out.println(a); // ④
      }
      
      结果:线程2打印结果为'0'与期望的'1'不同
      原因:指令重排序了,先执行了'flag = true;',线程2识别到后立刻打印a的值'0',然后再执行'a = 1; '
      解决方法:'volatile boolean flag = false;',禁止指令重排序:保证 a = 1 一定发生在 flag = true 之前。 
    • 双重检查式的懒汉式单例模式

      java 复制代码
      class Singleton {
          private static Singleton instance;
      
          public static Singleton getInstance() {
              if (instance == null) {            // 1. 第一次检查
                  synchronized (Singleton.class) {
                      if (instance == null) {    // 2. 第二次检查
                          instance = new Singleton(); // 3. 创建对象
                      }
                  }
              }
              return instance;
          }
      }
      
      
      instance = new Singleton();不是一个原子操作,它会被编译为三步指令:
      1分配内存地址
      2调用构造方法初始化对象
      3将引用赋值给 instance
      
      而由于 指令重排序,可能会变成:
      1分配内存地址
      3将引用赋值给 instance
      2调用构造方法初始化对象
      这就意味着另一个线程可能在步骤2还没执行完时看到 instance 已经不为 null,从而拿到了一个"未初始化完成的对象"。
      解决方案:禁止重排序--'private static volatile Singleton instance;'

volatile无法保证原子性和线程安全。

要想保证原子性/线程安全,可以使用原子包java.util.concurrent.atomic中的类(如 AtomicInteger, AtomicLong 等),该类能够保证原子性的核心是因为提供了compareAndSet()方法,该方法提供了cas算法(无锁算法,svn/git都使用的这个算法)

JVM运行时的内存区域

将JVM在运行时的内存,划分为了五个部分:

  • 程序计数器(PC Register):存储当前线程正在执行的字节码地址,即下一条要执行的指令位置;如果是 native 方法(调用操作系统的底层 API),则值为空;是唯一不会产生内存溢出的区域。
  • Java 虚拟机栈(JVM Stack):每个方法被调用时,JVM 会创建一个栈帧(Frame),用于存储:
    • 局部变量表(Local Variables)
    • 操作数栈(Operand Stack)
    • 方法返回地址
  • 本地方法栈(Native Method Stack):为 JVM 调用本地(native)方法提供栈空间。
  • 堆(Heap):
    • 存放对象实例和数组。
    • 是虚拟机区域中最大的一块。
    • 是JVM 的垃圾回收器(GC)主要管理的区域。
    • 随着 JVM 启动而创建,JVM 关闭而销毁。
    • 堆的逻辑划分:新生代:老年代 = 1:2。
      • 年轻代(Young Generation)
        • 存放新创建的对象。
        • 年轻代包含 eden,s0,s1,eden:s0:s1=8:1:1。
        • 年轻代的使用率一般在90%,在使用时,只能使用一个eden和一个s区间。
        • 当对象"熬过"几次 GC 后,会被晋升到老年代。
      • 老年代(Old Generation)
        • 存放生命周期较长的对象(比如单例、缓存对象)。
        • 垃圾回收频率较低,但每次回收代价较高。
  • 方法区(Method Area):存放
    • 类的元数据(类名、父类、接口信息等)
    • 字段、方法数据
    • 字节码(Method 字节码)
    • 运行时常量池(Runtime Constant Pool)
    • 静态变量(static)
    • 即时编译器(JIT)编译后的代码缓存等
相关推荐
Scott9999HH4 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔4 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社5 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海5 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖6 小时前
JDK 26 新特性详解
java·开发语言
马优晨7 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区8 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大9 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai9 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说9 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json