JVM与JMM深度解析:从Java 8到Java 21的演进

文章目录

    • 第一部分:JVM基础概念与架构
      • JVM是什么?
      • JVM整体架构
      • 运行时数据区
      • 类加载机制
      • 执行引擎
    • 第二部分:Java内存模型(JMM)
      • 什么是Java内存模型
      • JMM的核心问题
      • 主内存与工作内存
      • 内存间交互操作
      • 重排序与happens-before原则
      • volatile关键字
      • synchronized关键字
    • 第三部分:Java 8到Java 21的JVM和JMM演进
      • Java 8到Java 11的变化
        • JVM架构变化
        • JMM相关变化
      • Java 11到Java 17的变化
        • JVM架构变化
        • JMM相关变化
      • Java 17到Java 21的变化
        • JVM架构变化
        • JMM相关结构化并发
    • 第四部分:垃圾回收器详解
      • G1垃圾回收器
        • 核心特性
        • G1的工作流程
        • 适用场景
        • 调优参数
      • ZGC垃圾回收器
        • 核心特性
        • ZGC的工作流程
        • 适用场景
        • 调优参数
      • Shenandoah垃圾回收器
        • 核心特性
        • Shenandoah的工作流程
        • 适用场景
        • 调优参数
      • 垃圾回收器性能对比
        • 停顿时间对比
        • 吞吐量对比
        • 内存开销对比
    • 第五部分:虚拟线程与结构化并发
      • 虚拟线程简介
        • 虚拟线程与平台线程的区别
        • 虚拟线程的使用场景
        • 虚拟线程的使用示例
      • 结构化并发API
        • 核心概念
        • 结构化并发的优势
        • 结构化并发的使用示例
    • 第六部分:面试常见问题与回答
      • Q1: G1、ZGC和Shenandoah的主要区别是什么?
      • Q2: 为什么ZGC能实现低于10ms的停顿时间?
      • Q3: G1收集器的"可预测停顿"是如何实现的?
      • Q4: Java 17相比Java 8在GC方面有哪些重要改进?
      • Q5: 什么是双亲委派模型?如何破坏双亲委派模型?
      • Q6: 什么是JIT编译器?它如何提高Java程序的性能?
      • Q7: Java内存模型中的happens-before原则是什么?
      • Q8: 虚拟线程与传统线程的区别是什么?
      • Q9: 元空间与永久代的区别是什么?
      • Q10: 如何选择合适的垃圾回收器?
    • 总结
    • 参考资料

在Java开发者的职业生涯中,理解JVM(Java虚拟机)和JMM(Java内存模型)是进阶的必经之路。无论是日常开发还是面试,这都是绕不开的话题。本文将从实用角度出发,带你深入了解JVM和JMM的核心概念,并对比Java 8、Java 11、Java 17和Java 21各版本的实现差异,帮助大家更好的了解JVM和JMM

第一部分:JVM基础概念与架构

JVM是什么?

简单来说,JVM是一个抽象的计算机,它提供了一个平台无关的执行环境,让Java程序可以"一次编写,到处运行"。当我们编写Java代码时,编译器会将源代码(.java文件)编译成字节码(.class文件),而JVM则负责解释执行这些字节码。

这种设计带来了两个关键优势:

  • 平台独立性:Java程序可以在任何安装了JVM的设备上运行
  • 安全性:JVM提供了一个受控的执行环境,限制了程序对系统资源的直接访问

JVM整体架构

JVM的架构可以分为三大部分:

  1. 类加载子系统:负责加载、链接和初始化类
  2. 运行时数据区:JVM内存模型,包括方法区、堆、Java栈、本地方法栈和程序计数器
  3. 执行引擎:包括即时编译器(JIT)、解释器和垃圾回收器

运行时数据区

JVM的内存区域划分是面试的高频考点,包括:

  1. 程序计数器

    • 当前线程执行的字节码指令地址
    • 线程私有,是唯一不会发生OOM的内存区域
  2. Java虚拟机栈

    • 线程私有,生命周期与线程相同
    • 每个方法执行时会创建一个栈帧,包含局部变量表、操作数栈、动态链接和方法出口等信息
    • 可能抛出StackOverflowError和OutOfMemoryError
  3. 本地方法栈

    • 为本地(Native)方法服务
    • 也可能抛出StackOverflowError和OutOfMemoryError
  4. Java堆

    • 线程共享,存储对象实例
    • 垃圾收集器管理的主要区域,也称为"GC堆"
    • 可能抛出OutOfMemoryError
  5. 方法区

    • 存储已被虚拟机加载的类信息、常量、静态变量等
    • 在HotSpot虚拟机中,JDK8前称为"永久代",JDK8后称为"元空间"
    • 可能抛出OutOfMemoryError

类加载机制

类加载过程分为三个阶段:

  1. 加载:查找并加载类的二进制数据
  2. 链接
    • 验证:确保加载的类符合JVM规范
    • 准备:为类的静态变量分配内存并设置初始值
    • 解析:将符号引用转换为直接引用
  3. 初始化:执行类构造器<clinit>()方法

类加载器遵循三个重要原则:

  • 委托机制:先让父加载器尝试加载
  • 可见性:子加载器可以看到父加载器加载的类,反之不行
  • 唯一性:同一个类(由类名和加载器共同决定)只会被加载一次

JVM提供了三种内置的类加载器:

  1. 启动类加载器(Bootstrap ClassLoader)

    • 负责加载Java核心类库
    • 由C++实现,是JVM的一部分
  2. 扩展类加载器(Extension ClassLoader)

    • 负责加载Java扩展类库
    • JDK 9后改名为平台类加载器(Platform ClassLoader)
  3. 应用类加载器(Application ClassLoader)

    • 负责加载应用程序classpath下的类

执行引擎

执行引擎是JVM的核心组件,负责执行字节码指令:

  1. 解释器:逐条解释执行字节码指令
  2. JIT编译器:将热点代码编译成本地机器码,提高执行效率
  3. 垃圾回收器:自动回收不再使用的内存

JVM采用混合模式执行:

  • 解释执行:启动快,执行慢
  • 编译执行:启动慢,执行快

通过热点代码探测,JVM能够在解释执行和编译执行之间取得平衡,实现"解释+编译"的混合执行模式。

第二部分:Java内存模型(JMM)

什么是Java内存模型

Java内存模型(Java Memory Model,简称JMM)是Java虚拟机规范中定义的一种内存模型,用于屏蔽各种硬件和操作系统的内存访问差异,让Java程序在各种平台下都能达到一致的内存访问效果。

JMM不是真实存在的物理内存区域,而是一种抽象概念,是一组规范,定义了程序中各个变量的访问方式。

JMM的核心问题

JMM主要解决了三个核心问题:

  1. 可见性:一个线程对共享变量的修改,能够及时被其他线程看到
  2. 原子性:一个操作或多个操作要么全部执行并且不会被中断,要么都不执行
  3. 有序性:程序执行的顺序按照代码的先后顺序执行

主内存与工作内存

在JMM中,所有的变量都存储在主内存(Main Memory)中,每个线程还有自己的工作内存(Working Memory):

  • 主内存:所有线程共享的内存区域,存储所有变量的主副本
  • 工作内存:每个线程私有的内存区域,存储该线程使用到的变量的副本

线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

内存间交互操作

JMM定义了8种操作来完成主内存和工作内存的交互:

  1. lock(锁定):作用于主内存的变量,把变量标识为一条线程独占状态
  2. unlock(解锁):作用于主内存的变量,释放变量的锁定状态
  3. read(读取):作用于主内存的变量,把变量的值从主内存传输到线程的工作内存
  4. load(载入):作用于工作内存的变量,把read操作从主内存中得到的变量值放入工作内存的变量副本中
  5. use(使用):作用于工作内存的变量,把工作内存中的变量值传递给执行引擎
  6. assign(赋值):作用于工作内存的变量,把执行引擎接收到的值赋给工作内存的变量
  7. store(存储):作用于工作内存的变量,把工作内存中的变量值传送到主内存中
  8. write(写入):作用于主内存的变量,把store操作从工作内存中得到的变量值放入主内存的变量中

重排序与happens-before原则

为了提高性能,编译器和处理器常常会对指令进行重排序,分为三种类型:

  1. 编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序
  2. 指令级并行重排序:现代处理器采用了指令级并行技术来将多条指令重叠执行
  3. 内存系统重排序:由于处理器使用缓存和读/写缓冲区,使得加载和存储操作看上去可能是在乱序执行

这些重排序可能导致多线程程序出现内存可见性问题。为此,JMM定义了happens-before原则,用于描述操作之间的内存可见性。如果一个操作happens-before另一个操作,那么第一个操作的结果对第二个操作可见。

主要的happens-before规则包括:

  1. 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作
  2. 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁
  3. volatile变量规则:对一个volatile变量的写,happens-before于任意后续对这个volatile变量的读
  4. 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C
  5. 线程启动规则:Thread对象的start()方法happens-before于该线程的任何动作
  6. 线程终止规则:线程中的所有操作都happens-before于其他线程检测到该线程已经终止

volatile关键字

volatile是Java提供的最轻量级的同步机制,它保证了:

  1. 可见性:对一个volatile变量的写,对其他线程立即可见
  2. 有序性:禁止指令重排序优化

volatile的内存语义:

  • 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存
  • 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,从主内存中读取共享变量

volatile适用于一写多读的场景,但不保证原子性。例如,volatile++操作不是原子的,因为它包含读取、计算、写入三个步骤。

synchronized关键字

synchronized是Java中的重量级锁,它可以保证被修饰的代码块或方法在同一时刻只能被一个线程执行,从而保证了:

  1. 原子性:确保多个操作作为一个整体不可分割
  2. 可见性:synchronized的可见性是由"对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)"这条规则获得的
  3. 有序性:synchronized保证了一个变量在同一个时刻只能由一个线程对其进行lock操作

synchronized的内存语义:

  • 进入synchronized块时,会清空工作内存中的共享变量值,从主内存中重新读取
  • 退出synchronized块时,会将工作内存中的共享变量值刷新到主内存中

第三部分:Java 8到Java 21的JVM和JMM演进

Java 8到Java 11的变化

JVM架构变化
  1. 永久代到元空间的转变

    • Java 8 将永久代(PermGen)完全移除,取而代之的是元空间(Metaspace)
    • 元空间使用本地内存而非JVM堆内存,默认情况下可以动态增长,减少了"java.lang.OutOfMemoryError: PermGen space"错误
    • 类的元数据信息放到本地内存,常量池和静态变量放到Java堆中
  2. 默认垃圾收集器变更

    • Java 8 默认使用Parallel垃圾收集器
    • Java 11 默认使用G1垃圾收集器,提供更好的延迟可预测性和更高的吞吐量
  3. G1收集器的改进

    • Java 11 中G1收集器实现了并行Full GC,大幅提高了Full GC的性能
    • 引入了基于NUMA感知的内存分配优化
  4. 统一JVM日志系统

    • Java 9 引入,Java 11 完善了统一的JVM日志系统
    • 提供了一致的命令行选项和日志输出格式,便于问题诊断
JMM相关变化
  1. 变量句柄(VarHandle)

    • Java 9 引入,Java 11 完善
    • 提供比反射更精细的内存访问控制,支持原子操作和内存屏障
    • 可以替代Unsafe类的部分功能,提供类型安全的方式访问对象字段和数组元素
  2. 内存管理优化

    • Java 10 开始,JVM能够识别容器的内存限制(如Docker容器)
    • 改进了内存分配策略,更好地适应现代云环境

Java 11到Java 17的变化

JVM架构变化
  1. ZGC的引入与改进

    • Java 11 引入ZGC作为实验性功能
    • Java 15 将ZGC从实验特性转为产品特性
    • Java 17 进一步优化ZGC,提供更低的延迟和更高的吞吐量
    • ZGC支持并发类卸载、并发堆收缩,大幅减少停顿时间
  2. Shenandoah GC

    • Java 12 引入Shenandoah GC作为实验性功能
    • Java 15 将Shenandoah GC从实验特性转为产品特性
    • 专注于低停顿时间,适用于对响应时间敏感的应用
  3. CMS收集器的移除

    • Java 14 正式移除了CMS(Concurrent Mark Sweep)垃圾收集器
  4. 弹性元空间

    • Java 16 引入弹性元空间能力
    • 更高效地将未使用的HotSpot类元数据(即元空间)内存返回给操作系统
    • 减少了元空间内存占用,提高了内存利用率
JMM相关变化
  1. 增强的同步机制

    • Java 15 引入了改进的偏向锁实现
    • Java 16 开始弃用偏向锁,并在Java 18中完全移除
  2. 内存管理优化

    • 改进了G1和ZGC的内存回收策略
    • 优化了对大对象和长寿命对象的处理

Java 17到Java 21的变化

JVM架构变化
  1. 虚拟线程的引入

    • Java 19 引入虚拟线程作为预览特性
    • Java 21 正式发布虚拟线程
    • 虚拟线程是轻量级线程实现,由JVM而不是操作系统管理
    • 支持高吞吐量的并发应用,可以创建数百万个虚拟线程
  2. GC改进

    • 进一步优化ZGC和G1收集器
    • 改进了垃圾收集器的并行和并发能力
    • 降低了Full GC的频率和停顿时间
  3. JIT编译器优化

    • 改进了即时编译器的优化策略
    • 增强了逃逸分析和内联优化
    • 提高了代码执行效率
JMM相关结构化并发
  1. 结构化并发API

    • Java 19 引入结构化并发API作为预览特性
    • Java 21 继续改进结构化并发API
    • 提供了更简单、更可靠的并发编程模型
    • 更好地支持异步编程和错误处理
  2. 外部内存访问API

    • Java 14 引入外部内存访问API作为孵化器特性
    • Java 21 将其作为正式特性发布
    • 允许Java程序安全高效地访问Java堆外的内存

第四部分:垃圾回收器详解

G1垃圾回收器

G1(Garbage-First)是一种面向服务端应用的垃圾回收器,目标是替代CMS。

核心特性
  1. 分区(Region)设计:将堆内存划分为多个大小相等的区域,每个区域可以是Eden、Survivor、Old或Humongous区域
  2. 可预测的停顿时间:通过-XX:MaxGCPauseMillis参数设置目标停顿时间
  3. 混合收集:既有年轻代收集,也有老年代收集
  4. 增量收集:不需要一次性收集整个堆,而是选择部分区域进行收集
G1的工作流程
  1. 初始标记(Initial Marking):标记GC Roots能直接关联到的对象,需要STW(Stop The World)
  2. 并发标记(Concurrent Marking):对堆中对象进行可达性分析,与应用线程并发执行
  3. 最终标记(Final Marking):处理并发标记阶段遗留的少量SATB(Snapshot At The Beginning)记录,需要STW
  4. 筛选回收(Live Data Counting and Evacuation):对各个Region的回收价值和成本进行排序,根据用户期望的停顿时间制定回收计划,需要STW
适用场景
  1. 需要较低GC延迟的大型应用(堆内存4GB-32GB)
  2. 电商、金融等对响应时间敏感的在线业务系统
  3. 多核CPU环境
调优参数
-XX:+UseG1GC                   // 使用G1垃圾收集器
-XX:MaxGCPauseMillis=200       // 设置目标最大停顿时间为200毫秒
-XX:InitiatingHeapOccupancyPercent=45  // 设置触发标记周期的堆占用率阈值
-XX:G1NewSizePercent=5         // 设置年轻代最小占堆的百分比
-XX:G1MaxNewSizePercent=60     // 设置年轻代最大占堆的百分比

ZGC垃圾回收器

ZGC(Z Garbage Collector)是一款低延迟垃圾收集器,目标是在任何堆大小下都能将停顿时间控制在10ms以内。

核心特性
  1. 并发处理:几乎所有GC操作都与应用线程并发执行,包括标记、转移和重定位
  2. 基于区域的内存管理:类似G1,但区域大小动态可变(2MB-4TB)
  3. 标记-复制算法:使用复制算法进行内存整理
  4. 颜色指针:利用64位指针中的一些位作为标记位,实现快速对象定位和并发转移
  5. 支持超大堆:最高支持16TB的堆内存
ZGC的工作流程
  1. 并发标记:标记存活对象,与应用线程并发执行
  2. 并发转移准备:选择要清理的区域,与应用线程并发执行
  3. 并发转移:将存活对象从选定区域复制到新区域,与应用线程并发执行
  4. 并发重映射:更新引用,指向对象的新位置,与应用线程并发执行
适用场景
  1. 对延迟极为敏感的应用(如金融交易、游戏服务器)
  2. 大内存应用(堆内存>32GB)
  3. 需要稳定响应时间的实时系统
调优参数
-XX:+UseZGC                    // 使用ZGC垃圾收集器
-XX:ZAllocationSpikeTolerance=2 // 设置内存分配峰值容忍度
-XX:+UnlockExperimentalVMOptions // 解锁实验性VM选项(Java 15之前需要)
-XX:ConcGCThreads=2            // 设置并发GC线程数

Shenandoah垃圾回收器

Shenandoah是一款低延迟垃圾收集器,与ZGC类似,但实现方式不同。

核心特性
  1. 并发整理:与ZGC类似,支持并发标记和并发整理
  2. Brooks转发指针:每个对象都有一个额外的引用字段,指向对象的当前位置
  3. 不分代:默认不使用分代收集
  4. 连接矩阵:替代G1的记忆集,降低内存开销
Shenandoah的工作流程
  1. 初始标记:标记GC Roots直接引用的对象,需要STW
  2. 并发标记:递归标记整个对象图,与应用线程并发执行
  3. 最终标记:处理剩余的SATB缓冲区,需要STW
  4. 并发清理:回收确定为垃圾的区域,与应用线程并发执行
  5. 并发整理:将存活对象复制到新的内存区域,与应用线程并发执行
适用场景
  1. 需要低延迟但内存不是特别大的应用(堆内存8GB-32GB)
  2. 对延迟敏感的Web应用和服务
  3. 混合工作负载的应用
调优参数
-XX:+UseShenandoahGC           // 使用Shenandoah垃圾收集器
-XX:ShenandoahGCHeuristics=adaptive // 设置启发式策略
-XX:+UnlockExperimentalVMOptions // 解锁实验性VM选项(Java 15之前需要)
-XX:ConcGCThreads=2            // 设置并发GC线程数

垃圾回收器性能对比

停顿时间对比
  1. Parallel GC:百毫秒级,与堆大小成正比
  2. G1 GC:目标通常为100-200毫秒,但可能出现偶发的长时间停顿
  3. Shenandoah:通常在10-100毫秒之间,与堆大小关系较小
  4. ZGC:通常小于10毫秒,几乎与堆大小无关
吞吐量对比
  1. Parallel GC:最高(约99%)
  2. G1 GC:中高(约95-98%)
  3. Shenandoah:中等(约90-95%)
  4. ZGC:中等(约90-95%),但在Java 17后有显著提升
内存开销对比
  1. Parallel GC:最低
  2. G1 GC:中等(记忆集占用额外内存)
  3. Shenandoah:中高(Brooks转发指针占用额外内存)
  4. ZGC:中高(颜色指针机制占用额外内存)

更详细的介绍可以参考另一篇博文

第五部分:虚拟线程与结构化并发

虚拟线程简介

虚拟线程是Java 21引入的重要特性,它是一种轻量级线程实现,由JVM而不是操作系统管理。虚拟线程的设计目标是提高应用程序的吞吐量,特别是在处理大量并发任务时。

虚拟线程与平台线程的区别
  1. 创建成本:虚拟线程的创建成本远低于平台线程
  2. 内存占用:虚拟线程的内存占用很小,可以创建数百万个
  3. 调度方式:虚拟线程由JVM调度,而平台线程由操作系统调度
  4. 阻塞行为:虚拟线程阻塞时不会阻塞底层的平台线程
虚拟线程的使用场景
  1. 高并发服务器:处理大量并发连接
  2. 微服务架构:每个请求使用一个虚拟线程处理
  3. 异步编程:简化异步编程模型,使代码更易于理解和维护
虚拟线程的使用示例
// 创建并启动一个虚拟线程
Thread vThread = Thread.startVirtualThread(() -> {System.out.println("Running in a virtual thread");
});// 使用虚拟线程执行器
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {IntStream.range(0, 10_000).forEach(i -> {executor.submit(() -> {// 任务代码return i;});});
}

结构化并发API

结构化并发API是Java 19引入的预览特性,在Java 21中继续改进。它提供了一种更简单、更可靠的并发编程模型,特别适合与虚拟线程配合使用。

核心概念
  1. 结构化任务作用域:定义任务的生命周期和边界
  2. 子任务:在作用域内创建的任务
  3. 作用域关闭:确保所有子任务在作用域关闭前完成
结构化并发的优势
  1. 简化错误处理:子任务的异常会传播到父作用域
  2. 自动取消:当一个子任务失败时,其他子任务会被自动取消
  3. 避免资源泄漏:确保所有子任务在作用域关闭前完成或取消
  4. 提高代码可读性:使并发代码的结构更加清晰
结构化并发的使用示例
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {Future<String> user = scope.fork(() -> findUser(userId));Future<Integer> order = scope.fork(() -> fetchOrder(orderId));scope.join();           // 等待所有任务完成scope.throwIfFailed();  // 如果有任务失败,抛出异常// 使用结果processUserOrder(user.resultNow(), order.resultNow());
}

第六部分:面试常见问题与回答

Q1: G1、ZGC和Shenandoah的主要区别是什么?

回答

  • G1采用分区设计,兼顾吞吐量和停顿时间,适合中大型堆内存应用
  • ZGC专注于极低延迟,使用颜色指针技术,适合大内存和对延迟极为敏感的应用
  • Shenandoah也专注于低延迟,使用Brooks转发指针,适合中等内存和混合工作负载

Q2: 为什么ZGC能实现低于10ms的停顿时间?

回答:ZGC通过颜色指针技术和并发处理实现极低停顿。颜色指针利用64位指针中的一些位作为标记位,使得对象定位和转移能够并发执行,几乎所有GC操作(包括标记、转移和重定位)都与应用线程并发进行,只有极少数操作需要STW。

Q3: G1收集器的"可预测停顿"是如何实现的?

回答:G1通过以下机制实现可预测停顿:

  1. 将堆划分为多个大小相等的区域(Region)
  2. 维护每个区域的垃圾密度信息
  3. 根据用户设置的目标停顿时间(-XX:MaxGCPauseMillis)
  4. 在回收时优先选择回收价值最高(垃圾最多)的区域
  5. 动态调整年轻代大小和回收区域数量,以接近目标停顿时间

Q4: Java 17相比Java 8在GC方面有哪些重要改进?

回答

  1. 默认GC从Parallel变为G1,更注重延迟而非单纯吞吐量
  2. 引入并完善了ZGC和Shenandoah两款低延迟收集器
  3. 移除了老旧的CMS收集器
  4. G1收集器得到多项优化,包括并行Full GC、NUMA感知等
  5. 引入弹性元空间,优化了元空间的内存管理
  6. 改进了GC的日志和监控能力

Q5: 什么是双亲委派模型?如何破坏双亲委派模型?

回答:双亲委派模型是指当一个类加载器收到类加载请求时,它首先不会自己尝试加载这个类,而是把这个请求委派给父类加载器去完成。只有当父加载器无法完成加载请求时,子加载器才会尝试自己加载。

破坏双亲委派模型的方式:

  1. 重写loadClass()方法:直接覆盖loadClass()方法,不再调用父类加载器
  2. 使用线程上下文类加载器:通过Thread.currentThread().getContextClassLoader()获取上下文类加载器,打破了类加载器的层次结构
  3. 使用OSGi等模块化系统:OSGi实现了自己的类加载器架构,允许同一个类在不同的模块中加载

Q6: 什么是JIT编译器?它如何提高Java程序的性能?

回答:JIT(Just-In-Time)编译器是JVM的一个组件,它在运行时将热点代码(频繁执行的代码)编译成本地机器码,从而提高执行效率。

JIT编译器提高性能的方式:

  1. 热点代码识别:通过计数器或采样识别频繁执行的代码
  2. 即时编译:将字节码编译成优化的本地机器码
  3. 内联优化:将方法调用直接内联到调用点,减少方法调用开销
  4. 逃逸分析:分析对象的作用域,优化内存分配和同步
  5. 循环优化:提取循环不变量,减少计算量
  6. 分层编译:结合解释执行和不同级别的编译优化,平衡启动速度和峰值性能

Q7: Java内存模型中的happens-before原则是什么?

回答:happens-before是Java内存模型中的一个核心概念,用于描述操作之间的内存可见性。如果操作A happens-before操作B,那么A的结果对B可见。

主要的happens-before规则包括:

  1. 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作
  2. 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁
  3. volatile变量规则:对一个volatile变量的写,happens-before于任意后续对这个volatile变量的读
  4. 线程启动规则:Thread对象的start()方法happens-before于该线程的任何动作
  5. 线程终止规则:线程中的所有操作都happens-before于其他线程检测到该线程已经终止
  6. 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C

Q8: 虚拟线程与传统线程的区别是什么?

回答

  1. 实现方式:虚拟线程由JVM实现和管理,传统线程(平台线程)由操作系统实现和管理
  2. 资源消耗:虚拟线程非常轻量,可以创建数百万个;平台线程较重,通常只能创建数千个
  3. 调度方式:虚拟线程使用协作式调度,平台线程使用抢占式调度
  4. 阻塞行为:虚拟线程阻塞时会让出底层的载体线程,而不会阻塞操作系统线程
  5. 栈大小:虚拟线程的栈可以动态增长,平台线程的栈大小是固定的
  6. 适用场景:虚拟线程适合IO密集型任务,平台线程适合CPU密集型任务

Q9: 元空间与永久代的区别是什么?

回答

  1. 存储位置:永久代位于JVM堆内存中,而元空间使用本地内存(Native Memory)
  2. 内存限制:永久代有固定大小限制,元空间默认可以动态增长,只受系统内存限制
  3. 垃圾回收:元空间的垃圾回收效率更高,主要针对类元数据的回收
  4. OOM风险:永久代容易发生"PermGen space"的OOM错误,元空间降低了这种风险
  5. 内容存储:永久代存储类元数据、常量池和静态变量,元空间只存储类元数据,常量池和静态变量移到了堆中

Q10: 如何选择合适的垃圾回收器?

回答:选择垃圾回收器应考虑以下因素:

  1. 应用特性:

    • 批处理、离线计算类应用:选择Parallel GC,获得最高吞吐量
    • 一般在线业务系统:选择G1 GC,平衡吞吐量和延迟
    • 对延迟敏感的在线服务:选择ZGC或Shenandoah,获得更低的延迟
  2. 内存大小:

    • 小内存(<4GB):Parallel GC或G1 GC
    • 中等内存(4GB-32GB):G1 GC或Shenandoah
    • 大内存(>32GB):ZGC
  3. Java版本:

    • Java 8:Parallel GC(默认)或CMS
    • Java 11:G1 GC(默认)或尝试实验性的ZGC
    • Java 17及以上:G1 GC(默认)、ZGC或Shenandoah(均为产品特性)

总结

JVM和JMM是Java开发者必须掌握的核心知识,它们直接影响着Java程序的性能、稳定性和可靠性。从Java 8到Java 21,JVM和JMM经历了多次重要更新,包括永久代到元空间的转变、垃圾回收器的演进、虚拟线程的引入等。
在实际开发和面试中,理解这些概念和变化不仅有助于编写高质量的Java代码,还能帮助我们更好地进行性能调优和问题排查。希望本文能够帮助你在技术面试中脱颖而出,同时也能在日常工作中更加得心应手地处理JVM相关问题。

参考资料

  1. Oracle官方文档:JDK 11 Release Notes
  2. Oracle官方文档:JDK 17 Release Notes
  3. OpenJDK Wiki:ZGC
  4. Oracle官方文档:Java Language Changes
  5. 《深入理解Java虚拟机》第3版,周志明著
  6. 《Java并发编程的艺术》,方腾飞、魏鹏、程晓明著

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/81681.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker 挂载卷并保存为容器

1 创建docker容器 使用镜像osrf/ros:humble-desktop-full-jammy创建并运行容器 sudo docker run -it --name ros2_humble osrf/ros:humble-desktop-full-jammy /ros_entrypoint.sh bash docker run -it -v d:\docker\ros2_humble:/root/ros2_ws osrf/ros:humble-desktop-fu…

无人机飞手共享接单平台

2025年&#xff0c;无人机已不再是“黑科技”的代名词。从农田喷洒到外卖配送&#xff0c;从航拍摄影到医疗急救&#xff0c;无人机正以惊人的速度渗透我们的生活。而在这场变革中&#xff0c; “无人机飞手共享接单平台” 成为连接技术与需求的核心枢纽。它不仅让专业飞手轻松…

【Web应用】若依框架:基础篇07功能详解-定时任务

文章目录 ⭐前言⭐一、讲解过程⭐二、动手实操⭐总结 标题详情作者JosieBook头衔CSDN博客专家资格、阿里云社区专家博主、软件设计工程师博客内容开源、框架、软件工程、全栈&#xff08;,NET/Java/Python/C&#xff09;、数据库、操作系统、大数据、人工智能、工控、网络、程序…

8.8 Primary ODSA service without ODSA Portal

主要ODSA服务&#xff08;不使用ODSA门户&#xff09; 以下场景描述如下情况&#xff1a; • 主ODSA客户端应用程序被允许用于该类型的主设备&#xff0c;且对终端用户启用&#xff08;已授权&#xff09;。 • 服务提供商&#xff08;SP&#xff09;能够在不涉及ODSA门户Web服…

深度检测与动态透明度控制 - 基于Babylon.js的遮挡检测实现解析

首先贴出实现代码&#xff1a; OcclusionFader.ts import { AbstractEngine, Material, type Behavior, type Mesh, type PBRMetallicRoughnessMaterial, type Scene } from "babylonjs/core"; import { OcclusionTester } from "../../OcclusionTester"…

openssl 使用生成key pem

好的&#xff0c;以下是完整的步骤&#xff0c;帮助你在 Windows 系统中使用 OpenSSL 生成私钥&#xff08;key&#xff09;和 PEM 文件。假设你的 openssl.cnf 配置文件位于桌面。 步骤 1&#xff1a;打开命令提示符 按 Win R 键&#xff0c;打开“运行”对话框。输入 cmd&…

音视频之视频压缩及数字视频基础概念

系列文章&#xff1a; 1、音视频之视频压缩技术及数字视频综述 一、视频压缩编码技术综述&#xff1a; 1、信息化与视频通信&#xff1a; 什么是信息&#xff1a; 众所周知&#xff0c;人类社会的三大支柱是物质、能量和信息。具体而言&#xff0c;农业现代化的支柱是物质&…

传统数据表设计与Prompt驱动设计的范式对比:以NBA投篮数据表为例

引言&#xff1a;数据表设计方法的演进 在数据库设计领域&#xff0c;传统的数据表设计方法与新兴的Prompt驱动设计方法代表了两种截然不同的思维方式。本文将以NBA赛季投篮数据表(shots)的设计为例&#xff0c;深入探讨这两种方法的差异、优劣及适用场景。随着AI技术在数据领…

XCTF-web-mfw

发现了git 使用GitHack下载一下源文件&#xff0c;找到了php源代码 <?phpif (isset($_GET[page])) {$page $_GET[page]; } else {$page "home"; }$file "templates/" . $page . ".php";// I heard .. is dangerous! assert("strpos…

Prompt Tuning与自然语言微调对比解析

Prompt Tuning 与输入提示词自然语言微调的区别和联系 一、核心定义与区别 维度Prompt Tuning(提示微调)输入提示词自然语言微调本质优化连续向量空间中的提示嵌入(不可直接阅读)优化离散自然语言文本(人类可理解)操作对象模型输入嵌入层的连续向量(如WordEmbedding)自…

LVS的DR模式部署

目录 一、引言&#xff1a;高并发场景下的流量调度方案 二、LVS-DR 集群核心原理与架构设计 &#xff08;一&#xff09;工作原理与数据流向 数据包流向步骤3&#xff1a; &#xff08;二&#xff09;模式特性与53网络要求 三、实战配置&#xff1a;从9环境搭建到参数调整…

8种常见数据结构及其特点简介

一、8种常见数据结构 1. 数组&#xff08;Array&#xff09; 简介&#xff1a;数组是有序元素的序列&#xff0c;连续内存块存储相同类型元素&#xff0c;通过下标直接访问。数组会为存储的元素都分配一个下标&#xff08;索引&#xff09;&#xff0c;此下标是一个自增连续的…

通过mailto:实现web/html邮件模板唤起新建邮件并填写内容

一、背景 在实现网站、html邮件模板过程中&#xff0c;难免会遇到需要通过邮箱向服务提供方发起技术支持等需求&#xff0c;因此&#xff0c;我们需要通过一个功能&#xff0c;能新建邮件并提供模板&#xff0c;提高沟通效率 二、mailto协议配置说明 参数描述mailto:nameema…

好用但不常用的Git配置

参考文章 文章目录 tag标签分支新仓库默认分支推送 代码合并冲突处理默认diff算法 tag标签 默认是以字母顺序排序&#xff0c;这会导致一些问题&#xff0c;比如0.5.101排在0.5.1000之后。为了解决这个问题&#xff0c;我们可以把默认排序改为数值排序 git config --global t…

第六十八篇 从“超市收银系统崩溃”看JVM性能监控与故障定位实战

目录 引言&#xff1a;当技术问题遇上生活场景一、JVM的“超市货架管理哲学”二、收银员工具箱&#xff1a;JVM监控三板斧三、典型故障诊断实录四、防患于未然的运维智慧五、结语&#xff1a;从故障救火到体系化防控 引言&#xff1a;当技术问题遇上生活场景 想象一个周末的傍…

tauri2项目打开某个文件夹,类似于mac系统中的 open ./

在 Tauri 2 项目中打开文件夹 在 Tauri 2 项目中&#xff0c;你可以使用以下几种方法来打开文件夹&#xff0c;类似于 macOS 中的 open ./ 命令功能&#xff1a; 方法一&#xff1a;使用 shell 命令 use tauri::Manager;#[tauri::command] async fn open_folder(path: Strin…

编译pg_duckdb步骤

1. 要求cmake的版本要高于3.17&#xff0c;可以通过下载最新的cmake的程序&#xff0c;然后设置.bash_profile的PATH环境变量&#xff0c;将最新的cmake的bin目录放到PATH环境变量的最前面 2. g的版本要支持c17标准&#xff0c;否则会报 error ‘invoke_result in namespace ‘…

GO 语言中变量的声明

Go 语言变量名由字母、数字、下划线组成&#xff0c;其中首个字符不能为数字。Go 语言中关键字和保留字都不能用作变量名。Go 语言中的变量需要声明后才能使用&#xff0c;同一作用域内不支持重复声明。 并且 Go 语言的变量声明后必须使用。 1. var 声明变量 在 Go 语言中&…

windows和mac安装虚拟机-详细教程

简介 虚拟机&#xff1a;Virtual Machine&#xff0c;虚拟化技术的一种&#xff0c;通过软件模拟的、具有完整硬件功能的、运行在一个完全隔离的环境中的计算机。 在学习linux系统的时候&#xff0c;需要安装虚拟机&#xff0c;在虚拟机上来运行操作系统&#xff0c;因为我使…

XCTF-web-Cat

尝试输入127.0.0.1 尝试127.0.0.1;ls 试了很多&#xff0c;都错误&#xff0c;尝试在url里直接输入&#xff0c;最后发现输入%8f报错 发现了Django和DEBUG 根据Django的目录&#xff0c;我们使用进行文件传递 尝试?url/opt/api/database.sqlite3&#xff0c;找到了flag