JVM核心机制:类加载×字节码引擎×垃圾回收机制
🚀前言
“为什么你的Spring应用启动慢?为什么GC总是突然卡顿?答案藏在JVM的核心机制里!
本文将用全流程图解+字节码案例,带你穿透三大核心机制:
- 类加载:双亲委派如何防止恶意代码入侵?
- 字节码执行:JVM怎样把
invokevirtual变成机器指令? - 垃圾回收:STW停顿如何从秒级优化到毫秒级?
无论你是:
- 被
ClassNotFoundException折磨的开发者 - 想优化
接口调用性能的架构师 - 面试被问
G1回收原理的求职者
这里都有你想要的硬核答案!
👀文章摘要
📌 核心内容:
✅ 类加载机制:
- 加载→验证→准备→解析→初始化的完整流程
- 双亲委派模型的安全逻辑与打破方法(Tomcat如何实现?)
- 自定义类加载器实战(热部署/模块化隔离)
✅ 字节码执行引擎:
- 栈帧内部的局部变量表与操作数栈如何协作?
- 方法调用指令对比(
invokestaticvsinvokevirtual) - JIT即时编译的触发条件与分层编译
✅ 垃圾回收机制:
- 对象存活的三色标记算法
- GC器演进史:从Serial到ZGC的停顿时间优化
- 内存泄漏的MAT分析实战
🔍 适合人群:
- 需要深度调优JVM的开发者
- 准备高难度面试的求职者
- 对Java底层原理好奇的技术极客
第一章 类加载机制:深入Java动态性的基石
1.1 类加载过程(加载 → 链接 → 初始化)
全流程图示:
阶段详解:
| 阶段 | 关键动作 | 示例 |
|---|---|---|
| 加载 | 查找字节码并创建Class对象 | 从JAR包读取.class文件 |
| 验证 | 检查魔数/版本号/字节码安全性 | 防止篡改的class文件注入 |
| 准备 | 分配静态变量内存并设默认值 | static int a=5 此时a=0 |
| 解析 | 将符号引用转为直接引用 | 将java/lang/Object转为内存地址 |
| 初始化 | 执行<clinit>(静态块和静态赋值) | static { a=5; }在此阶段执行 |
触发初始化的6种场景:
new实例化对象- 访问类的静态变量/方法(非final)
- 反射调用
Class.forName() - 子类初始化触发父类初始化
- JVM启动的主类
- 动态语言支持(如MethodHandle)
2.2 双亲委派模型(BootStrap → Ext → App)
委派链条:
工作流程:
- 收到加载请求后,先委托父加载器尝试
- 父加载器无法完成时,才自己加载
- 所有父加载器失败 → 抛出
ClassNotFoundException
设计优势:
✔ 安全防护:防止核心类被篡改(如自定义java.lang.String)
✔ 避免重复:保证类在JVM中的唯一性
✔ 灵活扩展:可通过重写findClass()打破委派
源码片段(ClassLoader.loadClass()):
protected Class<?> loadClass(String name, boolean resolve) {synchronized (getClassLoadingLock(name)) {// 1. 检查是否已加载Class<?> c = findLoadedClass(name);if (c == null) {try {// 2. 委托父加载器if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {}// 3. 父类无法加载时自行处理if (c == null) {c = findClass(name);}}return c;}
}
3.3 自定义类加载器实战
适用场景:
- 热部署(如Spring DevTools)
- 模块化隔离(OSGi/Tomcat多应用隔离)
- 加密class文件解密加载
实现步骤:
- 继承
ClassLoader类 - 重写
findClass()(非loadClass!) - 调用
defineClass()完成加载
示例:加载网络上的class文件
public class NetworkClassLoader extends ClassLoader {private String serverUrl;public NetworkClassLoader(String url) { this.serverUrl = url;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] classData = downloadClassData(name); // 从网络下载字节码return defineClass(name, classData, 0, classData.length);}private byte[] downloadClassData(String className) {// 模拟网络请求(实际可用HttpClient)String path = serverUrl + "/" + className.replace('.', '/') + ".class";return FakeHttpClient.get(path); // 返回字节数组}
}// 使用示例
ClassLoader loader = new NetworkClassLoader("http://my-server.com/classes");
Class<?> clazz = loader.loadClass("com.example.Demo");
打破双亲委派的正确方式:
// 重写loadClass方法(谨慎使用!)
@Override
protected Class<?> loadClass(String name, boolean resolve) {if (name.startsWith("com.myapp.")) {return findClass(name); // 对特定包跳过委派}return super.loadClass(name, resolve);
}
🚨 常见问题与解决方案
问题1:类冲突
java.lang.LinkageError: loader constraint violation
✅ 解决:检查不同类加载器加载的相同类
问题2:内存泄漏
✅ 预防:避免长生命周期加载器加载短生命周期类
问题3:热部署失效
✅ 技巧:使用自定义加载器 + 类卸载(需满足条件)
第二章 字节码执行引擎:解密JVM的运行时核心
2.1 栈帧结构
每个方法调用对应一个栈帧,包含三大部分:
1. 局部变量表(Local Variables)
- 存储内容:方法参数 + 局部变量
- 访问方式:通过索引(
0对应this,非静态方法专用) - 槽位复用:超出作用域的变量可被覆盖
示例方法:
public int add(int a, int b) {int c = a + b;return c;
}
对应的局部变量表:
| 索引 | 名称 | 类型 |
|---|---|---|
| 0 | this | Object |
| 1 | a | int |
| 2 | b | int |
| 3 | c | int |
2. 操作数栈(Operand Stack)
- LIFO结构:临时存储计算中间结果
- 深度限制:编译时确定(
max_stack属性) - 字节码指令:
iconst_1(压栈)、iadd(弹出两个int相加)
计算1+2的字节码流程:
iconst_1 // 栈:[1]
iconst_2 // 栈:[1, 2]
iadd // 栈:[3]
istore_3 // 存入局部变量c,栈:[]
3. 动态链接(Dynamic Linking)
- 作用:将符号引用(如
java/lang/Object)转为直接引用 - 实现:运行时通过方法区的类元数据解析
对比静态链接:
| 类型 | 解析时机 | 典型场景 |
|---|---|---|
| 静态链接 | 编译期 | 静态方法/私有方法 |
| 动态链接 | 运行期(首次调用时) | 虚方法(多态场景) |
2.2 方法调用指令
四大调用指令对比:
| 指令 | 适用方法 | 绑定时机 | 多态性 |
|---|---|---|---|
invokestatic | 静态方法 | 编译期 | ❌ |
invokespecial | 构造方法/私有方法 | 编译期 | ❌ |
invokevirtual | 实例方法 | 运行期 | ✅ |
invokeinterface | 接口方法 | 运行期 | ✅ |
invokedynamic | Lambda/动态语言 | 首次调用时 | ✅ |
invokevirtual实现多态的原理:
- 通过对象头找到实际类的方法表
- 在方法表中查找方法描述符
- 执行目标方法的字节码
示例字节码:
// 源代码:animal.eat();
aload_1 // 加载animal对象到操作数栈
invokevirtual #2 // 调用Animal.eat()
2.3 基于栈 vs 基于寄存器
JVM(栈架构)特点:
✅ 指令紧凑(操作码+少量参数)
✅ 可移植性强(不依赖硬件寄存器)
✅ 实现简单(HotSpot的C1编译器优化后接近寄存器性能)
寄存器架构(如x86)特点:
✅ 执行速度快(减少内存访问)
✅ 指令数量少(如add eax, ebx)
性能对比实验:
// 同样的a+b*c,两种架构指令对比
栈架构:
iload_1 // a
iload_2 // b
iload_3 // c
imul // b*c
iadd // a+b*c寄存器架构:
mov eax, [b]
mul [c]
add eax, [a]
🚨 常见问题
问题1:操作数栈溢出
// 递归调用导致栈深度超过-Xss限制
Exception in thread "main" java.lang.StackOverflowError
✅ 解决:优化递归为循环 或 增加-Xss参数
问题2:动态链接性能损耗
✅ 优化:JVM会缓存解析结果(常量池缓存)
第三章 垃圾回收机制:从算法到实战调优
3.1 对象存活判定
两种核心策略:
| 方法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 引用计数法 | 对象被引用时计数器+1,归零即回收 | 实时性高 | 循环引用问题(Python用) |
| 可达性分析 | 从GC Roots出发,不可达的对象判定可回收 | 解决循环引用 | 需要STW暂停 |
GC Roots包括:
- 虚拟机栈中的局部变量
- 方法区中的静态变量
- 本地方法栈中的Native引用
- 被同步锁持有的对象
示例:循环引用问题
class Node {Node next;
}
Node a = new Node(); // a.refCount=1
Node b = new Node(); // b.refCount=1
a.next = b; // b.refCount=2
b.next = a; // a.refCount=2
a = b = null; // a/b.refCount=1 → 内存泄漏!
3.2 垃圾回收算法
三大基础算法对比:
| 算法 | 过程 | 空间利用率 | 速度 | 适用场景 |
|---|---|---|---|---|
| 标记-清除 | 标记存活对象 → 清除未标记区域 | 中(有碎片) | 中等 | 老年代(CMS) |
| 复制 | 存活对象复制到新空间 → 清空旧空间 | 低(50%浪费) | 快 | 新生代(Serial) |
| 标记-整理 | 标记存活对象 → 压缩到内存一端 | 高(无碎片) | 慢 | 老年代(Parallel) |
内存布局示例(复制算法):
3.3 经典GC器演进
五代GC器特性对比:
| GC器 | 年代 | 算法 | 线程 | STW | 适用场景 |
|---|---|---|---|---|---|
| Serial | 单代 | 复制/标记-整理 | 单线程 | 长暂停 | 客户端小应用 |
| Parallel | 分代 | 多线程复制/标记-整理 | 多线程 | 中暂停 | 吞吐优先型应用 |
| CMS | 老年代 | 并发标记-清除 | 并发 | 短暂停 | 低延迟Web服务 |
| G1 | 全堆 | 分Region标记-整理 | 并发/并行 | 可预测暂停 | 大内存混合负载 |
| ZGC | 全堆 | 染色指针+读屏障 | 并发 | <1ms暂停 | 超低延迟金融系统 |
CMS vs G1工作流程:
🚨 调优实战指南
1. 参数配置模板
# G1调优示例(JDK8+)
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=45
2. 选择GC器的决策树
3. 常见问题解决
- 频繁Full GC:检查老年代占用率(
jstat -gcutil) - Young GC耗时高:调整
-Xmn或-XX:NewRatio - MetaSpace溢出:增加
-XX:MaxMetaspaceSize
🎉结尾
“理解JVM核心机制,才能写出真正的‘Java高手代码’! 🚀
学完本系列后,你将能够:
- 🛠️ 诊断类加载冲突(比如Spring和Hibernate的jar包打架)
- ⚡ 通过字节码分析性能瓶颈(比如Lambda表达式的隐藏成本)
- 🔍 根据业务场景选择最佳GC器(电商低延迟 vs 大数据高吞吐)
记住:JVM不是黑箱,而是可观测、可优化的精密系统。
PS:如果你在学习过程中遇到问题,别慌!欢迎在评论区留言,我会尽力帮你解决!😄
相关文章:
JVM核心机制:类加载×字节码引擎×垃圾回收机制
🚀前言 “为什么你的Spring应用启动慢?为什么GC总是突然卡顿?答案藏在JVM的核心机制里! 本文将用全流程图解字节码案例,带你穿透三大核心机制: 类加载:双亲委派如何防止恶意代码入侵ÿ…...
opencv无法设置禁用RGB转换问题
树莓派连接摄像头,摄像头输出格式为YUYV(YUV422)。 通过执行 v4l2-ctl --list-formats --device/dev/video0 可以看的具体的摄像头的数据格式。 使用opencv获取视频流,通过cap.set(cv2.CAP_PROP_CONVERT_RGB, 0)设置禁用自动转换RGB格式,但是打印输出…...
k8s 1.30.6版本部署(使用canal插件)
#系统环境准备 参考 https://blog.csdn.net/dingzy1/article/details/147062698?spm1001.2014.3001.5501 #配置下载源 curl -fsSL https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.30/deb/Release.key |gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyri…...
GZ036区块链卷一 EtherStore合约漏洞详解
题目 pragma solidity >0.8.3;contract EtherStore {mapping(address > uint) public balances;function deposit() public payable {balances[msg.sender] msg.value;emit Balance(balances[msg.sender]);}function withdraw() public {uint bal balances[msg.sender…...
MCP+Blender创建电力塔
MCP(Model Context Protocol)与Blender的结合是当前AI与3D建模领域的热门技术,它通过协议化的方式让Claude等AI模型直接控制Blender,实现自动化3D建模。 1. 功能与原理 • 核心能力:用户通过自然语言指令(…...
什么是RACI矩阵,应用在什么场景?
一、什么是RACI RACI矩阵是一种用于明确项目或任务中角色与责任的管理工具,通过定义不同人员在任务中的参与程度来避免职责不清的问题。以下是其核心要点: RACI的含义 ● R(Responsible)执行者:直接完成任务…...
Selenium自动化:玩转浏览器,搞定动态页面爬取
嘿,各位爬虫爱好者和自动化达人们!是不是经常遇到这种情况:信心满满地写好爬虫,requests一把梭,结果抓下来的HTML里,想要的数据空空如也?定睛一看,原来数据是靠JavaScript动态加载出…...
QAI AppBuilder 快速上手(8): 图像修复应用实例2
LaMa-Dilated模型旨在通过扩张卷积技术实现高效的图像擦除和修复。该模型采用先进的卷积神经网络架构,能够处理复杂的图像输入,并填补图像中的缺失部分,使修复后的图像更加自然和逼真。LaMa-Dilated不仅在图像编辑领域表现出色,还…...
`ConstantPositionProperty` 的使用与应用
ConstantPositionProperty 的使用与应用 1. 什么是 ConstantPositionProperty? ConstantPositionProperty 是 Cesium 中用于表示实体位置的属性类。它表示一个实体在三维空间中的位置是固定的,不会随时间变化。与动态位置属性(如 SampledPo…...
【计网】作业4
一. 单选题(共22题,64分) 1. (单选题)主机甲采用停止-等待协议向主机乙发送数据,数据传输速率是4kb/s,单向传播时延为30ms,忽略确认帧的发送时延。当信道利用率等于80%时,数据帧的长度为&#…...
MPDrive:利用基于标记的提示学习提高自动驾驶的空间理解能力
25年4月来自南方科技大学、百度、英国 KCL和琶洲实验室(广东 AI 和数字经济实验室)的论文“MPDrive: Improving Spatial Understanding with Marker-Based Prompt Learning for Autonomous Driving”。 自动驾驶视觉问答(AD-VQA)…...
QTSql全解析:从连接到查询的数据库集成指南
概览 与数据库的有效集成是确保数据管理效率和应用性能的关键,Qt框架就提供了强大的QtSql模块,使得开发者能够轻松地进行数据库操作,包括连接、查询执行以及结果处理等 一、引入QtSql模块 首先,需要在项目中引入QtSql模块&…...
FreeRTOS临界区
在FreeRTOS中,临界区通过关闭可管理的中断来保护共享资源,具体关闭的中断层级由configMAX_SYSCALL_INTERRUPT_PRIORITY宏定义决定。以下是关键点解析: 中断优先级分类: 高优先级中断:数值低于configMAX_SYSCALL_INTERR…...
【学习笔记】HTTP和HTTPS的核心区别及工作原理
一、基础概念 HTTP(超文本传输协议):明文传输数据,默认端口80,容易被窃听或篡改。 HTTPS(HTTP SSL/TLS):通过加密传输数据,默认端口443,保障安全性。 二、…...
Dubbo的简单介绍
Dubbo的简单介绍 Dubbo 是一个高性能的 Java RPC 框架,最初由阿里巴巴开发,用于构建分布式服务。它主要用于提供服务间的通信,支持高效的远程调用和服务治理,常用于大规模分布式系统中。Dubbo 提供了以下几个核心功能:…...
7.2 重复推送(每日、每周等)
1. 核心方法 使用 UNCalendarNotificationTrigger 的 dateMatching 参数配置日历组件(DateComponents),结合 repeats: true 实现周期性触发。 2. 不同频率的重复推送配置 2.1 每日重复 每天固定时间触发(如上午 10:00ÿ…...
【STL】list介绍(附与vector的比较)
文章目录 1.关于list2.使用2.1 list的构造2.2 list 迭代器的使用2.3 list 容量操作2.3.1 size()2.3.2 empty()2.3.3 resize() 2.4 list 元素访问2.4.1 front()2.4.2 back() 2.5 list 修改操作2.5.1 push_front()2.5.2 pop_front()2.5.3 push_back()2.5.4 pop_back()2.5.5 inser…...
Ansible:roles角色
文章目录 Roles角色Ansible Roles目录编排Roles各目录作用创建 roleplaybook调用角色调用角色方法1:调用角色方法2:调用角色方法3: roles 中 tags 使用实战案例 Roles角色 角色是ansible自1.2版本引入的新特性,用于层次性、结构化…...
找不到导入的项目“xxx\QtMsBuild\Qt.props”。请确认 Import 声明“$(QtMsBuild)\Qt.props”中计算结果为
系列文章目录 文章目录 系列文章目录前言一、问题原因 前言 新建的项目visual studio2022 使用Qt vs tools 找不到导入的项目“E:\osgEarth\DigitalSimulationPlatform\DigitalSimulationPlatform\QtMsBuild\Qt.props”。 请确认 Import 声明“$(QtMsBuild)\Qt.props”中计算结…...
Rust 是如何层层防错的
一、Rust 的多层防错机制 🧱 第一层:Rust语言自带的“编译时护盾” —— 错误连运行都跑不起来 错误类型Rust 怎么发现的?工具/机制举个例子✅ 语法缺陷写错了代码格式或语法Rust Analyzer(智能补全)少写了分号、括号…...
SQL Server 数据库邮件配置失败:SMTP 连接与权限问题
问题现象: 配置数据库邮件时,发送测试邮件失败,提示 “邮件无法发送到 SMTP 服务器,操作超时”(错误 14661)或 “服务器拒绝发件人地址”(错误 15009)。 快速诊断 检查数据库邮件配置…...
2025 年福建交安安全员考试:结合本省交通特点备考
福建地处东南沿海,交通建设具有独特特点,这对交安安全员考试备考意义重大。在桥梁建设方面,由于面临复杂的海洋环境,桥梁的防腐、防台风等安全措施成为重点。考生在学习桥梁施工安全知识时,要特别关注福建本地跨海大桥…...
OpenBMC:BmcWeb 处理http请求5 检查权限
OpenBMC:BmcWeb 处理http请求4 处理路由对象-CSDN博客 在通过url获取了路由对象后,如果该请求是有session的,那么下一步需要检查权限 1.validatePrivilege调用时传入了一个lambda(1)做为回调 validatePrivilege(req, asyncResp, rule,[req, asyncResp, &rule, params =…...
996引擎-源码学习:Cocos2d-Lua 的 class(classname, ...)
996引擎-源码学习:Cocos2d-Lua 的 class(classname, ...) 一、核心方法调用顺序用户调用入口完整调用链二、__create 工厂方法的三种情形情形1:父类为函数(自定义工厂)情形2:父类为Cocos原生类情形3:父类为普通Lua表三、方法职责与内存管理对照表四、正确使用示例示例1…...
UE5 蓝图里的声音
文章目录 支持的格式设置循环播放在场景中放置音频设置音频的衰减与不衰减在UI动画中播放声音使用蓝图节点播放声音按钮本身就可以播放声音 支持的格式 支持:WAV 不支持:MP3 设置循环播放 双击音频,打开音频设置,勾选Looping …...
「合诚」携手企企通共建新材料和健康产业采购数智化新生态
在科技革命与产业变革深度融合的时代背景下,新材料与健康产业正迎来数字化、智能化的快速发展。 技术突破与消费升级的双重驱动,推动着行业不断创新,同时也对企业的供应链管理提出了更高要求。 1、合诚:聚焦新材料与健康产业&am…...
Three.js 系列专题 7:性能优化与最佳实践
内容概述 随着 3D 场景复杂度的增加,性能优化变得至关重要。Three.js 项目可能因几何体数量、纹理大小或渲染设置而变慢。本专题将介绍减少 draw call、优化纹理和使用调试工具的最佳实践。 学习目标 学会减少 draw call 和几何体复杂度。掌握纹理压缩与内存管理。使用 Stat…...
java+postgresql+swagger-多表关联insert操作(七)
入参为json,然后根据需要对多张表进行操作: 入参格式: [{"custstoreName":"swagger-测试经销商01","customerName":"swagger-测试客户01","propertyNo":"swaggertest01",&quo…...
Git版本管理系列:(一)使用Git管理单分支
目录 基础概念介绍仓库的创建创建隐藏目录添加代码到暂存区提交代码到仓库提交记录查询比较差异标签文件删除版本回退总结 Git 是一个分布式版本控制系统(DVCS),用于跟踪文件的变更并协调多人协作开发,由 Linus Torvalds 于 2…...
mapbox基础,加载ESRI OpenStreetMap开放街景标准风格矢量图
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.1 ☘️mapboxgl.Map style属性二、🍀加载ESRI OpenStreetMap开放街景标准风…...
