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)编译后的代码缓存等
相关推荐
hygge9992 小时前
JVM 内存结构、堆细分、对象生命周期、内存模型全解析
java·开发语言·jvm·经验分享·面试
小二·2 小时前
Java虚拟机(JVM)面试题(51道含答案)
java·开发语言·jvm
无敌最俊朗@2 小时前
03-事务高频面试总结
java·开发语言·jvm
hygge9992 小时前
类加载机制、生命周期、类加载器层次、JVM的类加载方式
java·开发语言·jvm·经验分享·面试
修行者Java2 小时前
JVM 垃圾回收算法的详细介绍
jvm·算法
一 乐2 小时前
海产品销售系统|海鲜商城购物|基于SprinBoot+vue的海鲜商城系统(源码+数据库+文档)
java·前端·javascript·数据库·vue.js·后端
mjhcsp3 小时前
C++ 三分查找:在单调与凸函数中高效定位极值的算法
开发语言·c++·算法
我命由我123453 小时前
Element Plus 组件库 - Select 选择器 value 为 index 时的一些问题
开发语言·前端·javascript·vue.js·html·ecmascript·js
沐知全栈开发3 小时前
MySQL 删除数据库指南
开发语言