JVM核心原理总结

一、栈上的数据存储

1.1 基本数据类型在栈上的实现

Java的8大基本数据类型在虚拟机中的实现方式与内存占用:

数据类型 堆内存占用(字节) 栈中slot数 虚拟机内部符号
byte 1 1 B
short 2 1 S
int 4 1 I
long 8 2 J
float 4 1 F
double 8 2 D
char 2 1 C
boolean 1 1 Z
  • 空间换时间:Java虚拟机采用空间换时间方案,在栈上不存储具体类型,只根据slot槽处理数据
  • 64位特殊处理:long和double类型在64位系统中占用2个slot,共16字节,但高8字节未使用,实际满足8字节需求

1.2 boolean类型的内部实现

  • 在栈上boolean类型与int类型相同处理,值1代表true,0代表false
  • 可通过ASM框架修改字节码指令,验证boolean在栈上实际可存储超过1的值
  • 从栈保存到堆上时,boolean类型只取低位的最后一位保存
  • 在堆上,boolean类型只占用1个字节,无符号,低位复制,高位补0

1.3 栈与堆数据转换规则

  • 堆→栈 :由于栈上空间大于或等于堆上空间,直接处理,注意符号位
    • boolean、char为无符号:低位复制,高位补0
    • byte、short为有符号:低位复制,非负补0,负则补1
  • 栈→堆 :高位需要截断
    • boolean特殊:只取低位的最后一位

二、对象在堆上的内存布局

2.1 对象内存结构

对象在堆中主要包含三部分:

  1. 对象头:包含Mark Word和Klass Pointer

  2. 实例数据:对象的实际字段

  3. 对齐填充:保证对象大小为8字节的倍数

    普通对象结构:
    ┌──────────────┐
    │ Mark Word │ 32位4字节, 64位8字节,保存锁、GC等信息
    ├──────────────┤
    │Klass Pointer │ 指向方法区中InstanceKlass对象的指针
    ├──────────────┤
    │ 实例数据 │ 字段重排序,保证内存对齐
    ├──────────────┤
    │ 对齐填充 │ 保证对象大小为8字节的倍数
    └──────────────┘

    数组对象额外包含:
    ┌──────────────┐
    │ 数组长度 │ 4字节(int)
    └──────────────┘

2.2 Mark Word(标记字段)

Mark Word在不同状态下存储不同内容,64位开启指针压缩布局:

未锁定状态

  • 2.5位未使用
  • 31位Hashcode
  • 1位未使用
  • 4位分代年龄
  • 1位偏向锁标记
  • 2位锁状态(01)

偏向锁状态

  • 54位线程ID
  • 2位epoch
  • 1位偏向锁标记
  • 4位分代年龄
  • 2位锁状态(01)

轻量级锁

  • 62位指向栈中锁记录的指针
  • 2位锁状态(00)

重量级锁

  • 62位指向Monitor对象的指针
  • 2位锁状态(10)

GC标记

  • 62位空
  • 2位锁状态(11)

2.3 指针压缩

  • 64位JVM中,堆中原本8字节的指针可压缩为4字节
  • 默认开启,可通过-XX:-UseCompressedOops关闭
  • 适用条件:堆大小不超过32GB(2^35字节)
  • 内存对齐:将对象起始地址对齐到8字节边界,便于指针压缩

2.4 内存对齐

  • 每个对象字节数必须是8的倍数
  • 字段偏移量(offset)需是字段长度的整数倍
  • 通过字段重排序和对齐填充实现
  • 目的:避免伪共享,提高CPU缓存效率

三、方法调用原理

3.1 五种字节码指令

JVM提供5种字节码指令执行方法调用:

  1. invokestatic:调用静态方法

    • 静态绑定
    • 编译期确定方法地址
  2. invokespecial:调用私有方法、构造方法,以及super关键字调用父类方法

    • 静态绑定
    • final修饰的invokevirtual也使用此方式
  3. invokevirtual:调用非私有实例方法

    • 非final方法使用动态绑定
    • 通过虚方法表(vtable)查找方法地址
  4. invokeinterface:调用接口方法

    • 动态绑定
    • 通过接口方法表(itable)查找方法地址
  5. invokedynamic:调用动态方法

    • 主要用于lambda表达式
    • 机制复杂,JVM 7引入

3.2 静态绑定 vs 动态绑定

  • 静态绑定

    • 编译期间确定方法地址
    • 适用于static、private、final方法
    • 方法第一次调用时,符号引用替换为直接内存地址
  • 动态绑定

    • 运行时确定方法地址
    • 适用于非static、非private、非final的实例方法
    • 通过虚方法表实现多态

3.3 虚方法表(vtable)

  • 每个类包含一个虚方法表,记录方法地址
  • 子类方法表包含父类所有方法
  • 重写方法时,用子类方法地址替换父类方法地址
  • 查找步骤:
    1. 根据对象头中的类型指针找到InstanceKlass
    2. 从InstanceKlass获取虚方法表
    3. 通过索引找到方法地址
    4. 调用方法

四、异常捕获原理

4.1 异常表

  • 编译期生成,存储异常处理信息
  • 包含四个关键字段:
    • 起始PC:异常捕获范围开始位置
    • 结束PC:异常捕获范围结束位置
    • 跳转PC:捕获异常后跳转的指令位置
    • 捕获类型:可捕获的异常类型

4.2 异常处理流程

  1. 异常发生时,JVM从上至下遍历异常表
  2. 检查异常发生位置是否在捕获范围内
  3. 检查异常类型是否匹配
  4. 如匹配,跳转到"跳转PC"位置
  5. 如不匹配,弹出当前栈帧,在上层栈帧继续查找

4.3 finally的实现

  • finally代码块会被复制到try和catch执行路径之后
  • 异常表增加额外条目处理Throwable等未捕获异常
  • 通过局部变量表保存异常,执行完finally后再抛出

五、JIT即时编译器

5.1 JIT基本原理

  • 热点代码:执行频率高的字节码
  • 将字节码编译成机器码,直接在CPU执行
  • 热点代码优化:方法内联、逃逸分析等

5.2 分层编译

JDK7后,HotSpot采用分层编译,5个优化级别:

等级 组件 描述 保存的信息
0 解释器 解释执行,记录方法/循环次数
1 C1编译器 基础优化 优化后代码
2 C1编译器 基础优化+收集信息 优化后代码+方法/循环次数
3 C1编译器 C1完整优化+收集所有额外信息 类型、分支概率等
4 C2编译器 深度优化,服务端代码优化 优化后代码
  • C1:编译速度快,优化效果较弱
  • C2:编译速度慢,优化效果强
  • Graal:新一代JIT编译器,替代C2

5.3 方法内联

  • 将方法体直接复制到调用方
  • 节省创建栈帧开销
  • 内联条件:
    • 方法字节码<325字节且是热点方法(-XX:FreqInlineSize)
    • 方法字节码<35字节直接内联(-XX:MaxInlineSize)
    • 生成机器码<1000字节(-XX:InlineSmallCode)
    • 接口实现类<3个

5.4 逃逸分析

  • 分析对象是否被外部方法/线程引用
  • 优化技术:
    1. 锁消除:对象不逃逸,消除同步锁
    2. 标量替换:将对象拆分为基本类型,在栈上分配
    3. 栈上分配:不逃逸对象分配在栈上,避免GC

六、垃圾回收器原理

6.1 G1垃圾回收器

6.1.1 年轻代回收
  • 只扫描Eden+Survivor区域
  • 问题:老年代对象可能引用年轻代对象
  • 解决方案:记忆集(RememberedSet) + 卡表(CardTable)

记忆集优化:

  1. 记录区域而非对象级别的引用
  2. 将内存划分卡页(512字节),记录卡页引用
  3. 通过写屏障维护卡表,标记"脏卡"

执行流程:

  1. Root扫描
  2. 处理脏卡队列,更新记忆集
  3. 标记存活对象
  4. 选择回收集合(Collection Set)
  5. 复制存活对象
  6. 处理引用和JNI弱引用
6.1.2 混合回收
  • 触发条件:堆占用率>45%
  • 步骤:
    1. 初始标记:STW,三色标记法标记GC Root可达对象
    2. 并发标记:与用户线程并发,继续标记
    3. 最终标记:STW,处理SATB(初始快照)队列
    4. 清理:STW,清除无存活对象的区域
    5. 转移:将存活对象复制到新区域
6.1.3 三色标记与SATB
  • 三色标记
    • 黑色:存活,引用关系已处理
    • 灰色:待处理,引用关系部分处理
    • 白色:可回收,不在GC Root链上
  • SATB技术
    • 初始创建快照,记录所有对象
    • 采用写前屏障,将旧引用对象加入SATB队列
    • 避免漏标,可能产生浮动垃圾

6.2 ZGC垃圾回收器

6.2.1 低延迟特性
  • STW时间始终<1ms
  • 支持堆大小:几百MB到16TB
  • 对象地址空间:44位,最大16TB
6.2.2 着色指针

将8字节指针拆分为三部分:

  • 最低44位:对象地址
  • 中间4位:颜色位,同一时间只有一位是1
    • Marked0/Marked1:标记可达对象
    • Remap:重映射位,引用关系已变更
    • Finalizable:只能通过终结器访问
  • 高16位:未使用
6.2.3 读屏障
  • 读取对象引用时触发
  • 检查是否需要重映射
  • 将引用指向转移后的对象
  • 用户线程协助GC工作
6.2.4 执行流程
  1. 初始标记:STW,标记GC Root直接引用对象
  2. 并发标记:标记所有可达对象
  3. 并发处理:选择转移区域,创建转移表
  4. 转移开始:STW,转移GC Root引用对象
  5. 并发转移:转移剩余对象
  6. 重映射:修正引用关系,用户线程协助
6.2.5 ZPage区域
  • 小区域:2MB,保存<256KB对象
  • 中区域:32MB,保存256KB-4MB对象
  • 大区域:保存>4MB单个对象

6.3 ShenandoahGC

6.3.1 1.0版本
  • 每个对象增加8字节前向指针
  • 读前屏障:根据前向指针访问转移后对象
  • 缺点:内存占用增加5-10%,性能影响大
6.3.2 2.0版本(主流)
  • 仅转移阶段将前向指针放入Mark Word
  • CAS确保并发安全
  • 执行流程与ZGC类似
6.3.3 分代支持
  • JDK21+支持分代Shenandoah
  • 年轻代和老年代回收可并行执行
  • 减少全堆扫描频率,提升性能

七、最佳实践建议

7.1 代码优化

  1. 小方法设计:便于JIT内联
  2. 控制接口实现类数量:不超过2个,保证内联
  3. 避免对象逃逸:高频方法中创建临时对象
  4. 自定义热点方法:JDK标准库中复杂方法可能无法内联

7.2 JVM参数调优

  1. JIT优化

    • -XX:MaxInlineSize:控制内联方法大小
    • -XX:FreqInlineSize:控制热点方法内联大小
    • -XX:InlineSmallCode:控制机器码内联大小
  2. G1回收器

    • -XX:MaxGCPauseMillis:期望最大停顿时间
    • -XX:InitiatingHeapOccupancyPercent:触发混合GC的堆占用率
  3. ZGC

    • -XX:+UseZGC:启用ZGC
    • -XX:ZAllocationSpikeTolerance:分配峰值容忍度

7.3 GC选择指南

  • 低延迟需求:ZGC或Shenandoah
  • 大堆应用(>64GB):ZGC
  • 吞吐量优先:G1或Parallel GC
  • JDK8环境:G1或CMS(即将废弃)
  • 容器化环境:ZGC(对容器友好)

八、总结

JVM是Java语言的核心运行环境,理解其内部原理对性能优化和问题排查至关重要。关键要点包括:

  1. 内存管理

    • 栈上slot管理保证执行效率
    • 堆上对象布局优化空间利用
    • 指针压缩减少64位系统内存开销
  2. 执行优化

    • JIT编译提升热点代码执行效率
    • 方法内联消除调用开销
    • 逃逸分析优化对象分配
  3. 垃圾回收演进

    • 从分代设计(G1)到并发转移(ZGC, Shenandoah)
    • 从Stop-The-World到亚毫秒级停顿
    • 从对象追踪到指针着色等创新技术
  4. 持续发展

    • ZGC支持TB级堆内存
    • 分代ZGC/Shenandoah提升吞吐量
    • Valhalla项目将带来值类型,进一步优化内存和性能
相关推荐
小肖爱笑不爱笑4 小时前
2025/12/16 HTML CSS
java·开发语言·css·html·web
q_19132846954 小时前
基于SpringBoot2+Vue2的装修报价网站
java·vue.js·spring boot·mysql·计算机毕业设计·演示文稿
C+++Python4 小时前
PHP 反射 API
android·java·php
xiaohua10094 小时前
JVM性能分析
java·jvm
AI视觉网奇4 小时前
live2d 单图转模型 单图生成模型
java·前端·python
a程序小傲4 小时前
淘宝Java面试被问:Atomic原子类的实现原理
java·开发语言·后端·面试
yuuki2332334 小时前
【C++】模板初阶
java·开发语言·c++
qq_12498707534 小时前
基于Spring Boot的社区医院管理系统的设计与实现(源码+论文+部署+安装)
java·数据库·人工智能·spring boot·毕业设计
醇氧4 小时前
Spring Boot 应用启动优化:自定义事件监听与优雅启动管理
java·开发语言·python