JVM架构全景图:五大核心组件详解

引言 为什么要学习这个主题? 在前两篇文章中,我们知道了Java程序如何运行,以及JVM的本质。但JVM内部到底是怎么工作的? 想象一下: 一个.class文件是如何被加载到JVM中的? 对象和变量存储在哪里? 字节码是如何被"翻译"成机器码的? 理解JVM的架构,就像理解一台计算机的组成(CPU、内存、硬盘)一样重要。这是后续学习类加载、内存管理、GC调优的基础。 你将学到什么? ✅ JVM的整体架构图 ✅ 五大核心组件的职责 ✅ 各组件如何协作运行Java程序 ✅ JVM的完整执行流程 一、JVM架构全景图 1.1 整体架构 ┌─────────────────────────────────────────────────────────────┐ │ Java应用程序 │ │ (.java → .class) │ └──────────────────────┬──────────────────────────────────────┘ │ 字节码 ↓ ┌─────────────────────────────────────────────────────────────┐ │ JVM(Java虚拟机) │ │ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 1️⃣ 类加载子系统 (Class Loader Subsystem) │ │ │ │ - 加载 (Loading) │ │ │ │ - 链接 (Linking): 验证、准备、解析 │ │ │ │ - 初始化 (Initialization) │ │ │ └────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 2️⃣ 运行时数据区 (Runtime Data Areas) │ │ │ │ │ │ │ │ 【线程共享】 【线程私有】 │ │ │ │ - 堆 (Heap) - 程序计数器 (PC Register)│ │ │ │ - 方法区 (Method Area) - 虚拟机栈 (VM Stack) │ │ │ │ - 本地方法栈 (Native Stack)│ │ │ └────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 3️⃣ 执行引擎 (Execution Engine) │ │ │ │ - 解释器 (Interpreter) │ │ │ │ - JIT编译器 (Just-In-Time Compiler) │ │ │ │ - 垃圾收集器 (Garbage Collector) │ │ │ └────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 4️⃣ 本地接口 (Native Interface - JNI) │ │ │ └────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 5️⃣ 本地方法库 (Native Method Libraries) │ │ │ └────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 操作系统 & 硬件 │ └─────────────────────────────────────────────────────────────┘ 1.2 核心组件概览 组件 作用 类比 类加载子系统 加载、链接、初始化类 快递员(把包裹送到仓库) 运行时数据区 存储数据(对象、变量、代码) 内存/仓库 执行引擎 执行字节码 CPU(执行指令) 本地接口 调用操作系统API 操作系统接口 垃圾收集器 自动回收无用对象 清洁工(清理垃圾) 二、类加载子系统(Class Loader Subsystem) 2.1 职责 负责将.class文件加载到JVM内存中,并准备好供使用。 ...

2025-11-20 · maneng

执行引擎:SQL执行的最后一步

执行引擎概述 执行引擎(Executor) 负责调用存储引擎接口,执行SQL,返回结果。 SQL执行完整流程: ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 连接器 │→│ 分析器 │→│ 优化器 │→│ 执行器 │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ ↓ 调用接口 ┌──────────┐ │ 存储引擎 │ └──────────┘ 执行引擎的职责 1. 权限检查 -- 查询前检查权限 SELECT * FROM users WHERE id = 1; -- 检查流程 1. 当前用户是否有users表的SELECT权限? 2. 如果没有 → 返回错误:Access denied -- 权限存储位置 mysql.user -- 全局权限 mysql.db -- 数据库权限 mysql.tables_priv -- 表权限 2. 调用存储引擎接口 -- 示例SQL SELECT * FROM users WHERE id = 1; -- 执行引擎操作 1. 打开表:handler = open_table("users") 2. 初始化查询:handler->index_init(id索引) 3. 定位第一行:handler->index_read(id=1) 4. 读取行数据:handler->read_row() 5. 关闭表:handler->close() 3. 结果返回 -- 返回流程 1. 存储引擎返回数据行 2. 执行引擎应用WHERE条件过滤 3. 格式化结果(MySQL协议) 4. 通过网络发送给客户端 -- 流式返回(Streaming) -- MySQL边查询边返回,不等全部数据查完 存储引擎接口(Handler API) 1. 打开表 // C代码示例(简化) int handler::ha_open(TABLE *table, const char *name, int mode); // 流程 1. 打开表文件(.ibd) 2. 加载表结构(.frm,MySQL 8.0已废弃) 3. 初始化Handler对象 2. 读取接口 // 全表扫描 int handler::rnd_init(); // 初始化全表扫描 int handler::rnd_next(uchar *buf); // 读取下一行 // 索引扫描 int handler::index_init(uint idx); // 初始化索引扫描 int handler::index_read(uchar *buf, const uchar *key, uint key_len); // 定位第一行 int handler::index_next(uchar *buf); // 读取下一行 3. 写入接口 // 插入 int handler::write_row(uchar *buf); // 更新 int handler::update_row(const uchar *old_data, const uchar *new_data); // 删除 int handler::delete_row(const uchar *buf); 执行流程详解 1. 全表扫描 -- SQL SELECT * FROM users WHERE age > 25; -- 执行流程 1. 打开表:open_table("users") 2. 初始化全表扫描:rnd_init() 3. 循环读取 WHILE (rnd_next(row) == 0) DO IF age > 25 THEN 发送到客户端 END IF END WHILE 4. 关闭表:close() -- 扫描行数:全表所有行 2. 索引扫描 -- SQL SELECT * FROM users WHERE id = 1; -- 执行流程(主键索引) 1. 打开表:open_table("users") 2. 初始化索引扫描:index_init(PRIMARY) 3. 定位第一行:index_read(id=1) 4. 读取行数据:read_row() 5. 发送到客户端 6. 关闭表:close() -- 扫描行数:1行(主键等值查询) 3. 索引范围扫描 -- SQL SELECT * FROM users WHERE id BETWEEN 1 AND 100; -- 执行流程 1. 打开表:open_table("users") 2. 初始化索引扫描:index_init(PRIMARY) 3. 定位第一行:index_read(id=1) 4. 循环读取 WHILE (index_next(row) == 0 AND id <= 100) DO 发送到客户端 END WHILE 5. 关闭表:close() -- 扫描行数:100行(range查询) JOIN执行流程 Nested Loop Join -- SQL SELECT * FROM orders o JOIN users u ON o.user_id = u.id; -- 执行流程(Nested Loop Join) 1. 打开orders表:open_table("orders") 2. 打开users表:open_table("users") 3. 初始化orders全表扫描:orders->rnd_init() 4. 循环orders表 FOR EACH order IN orders DO -- 内层:通过索引查找users表 users->index_read(id = order.user_id) users->read_row() -- 拼接结果,发送客户端 END FOR 5. 关闭表 -- 扫描次数 orders表:全表扫描(M行) users表:索引查找(M次,每次O(log N)) 总复杂度:O(M × log N) 执行器优化 1. 条件下推 -- SQL SELECT * FROM users WHERE id > 100; -- 未优化 1. 存储引擎返回所有行 2. 执行器过滤 id > 100 3. 返回结果 -- ICP优化(Index Condition Pushdown) 1. 存储引擎扫描索引时,直接过滤 id > 100 2. 返回满足条件的行 3. 减少回表次数 2. 批量读取 -- Multi-Range Read(MRR)优化 -- 场景:范围查询 + 回表 -- 未优化 1. 扫描索引:获取10个主键ID(随机顺序) 2. 逐个回表:10次随机IO -- MRR优化 1. 扫描索引:获取10个主键ID 2. 排序主键ID(变为顺序) 3. 批量回表:1次顺序IO 执行器与优化器的配合 -- SQL SELECT * FROM users WHERE name = 'Alice' AND age = 25; -- 优化器决策 1. 选择索引:idx_name_age 2. 生成执行计划: - type: ref - key: idx_name_age - rows: 1 -- 执行器执行 1. 调用存储引擎接口:index_read(idx_name_age, 'Alice', 25) 2. 存储引擎返回数据行 3. 执行器返回客户端 查看执行器操作 1. SHOW PROFILE -- 开启profiling SET profiling = 1; -- 执行SQL SELECT * FROM users WHERE id = 1; -- 查看执行耗时 SHOW PROFILES; -- 详细分析 SHOW PROFILE FOR QUERY 1; -- 输出示例 +----------------------+----------+ | Status | Duration | +----------------------+----------+ | starting | 0.000050 | | checking permissions | 0.000005 | | Opening tables | 0.000020 | | init | 0.000010 | | System lock | 0.000005 | | optimizing | 0.000005 | | statistics | 0.000010 | | preparing | 0.000010 | | executing | 0.000005 | ← 执行器调用存储引擎 | Sending data | 0.000050 | ← 存储引擎返回数据 | end | 0.000005 | | query end | 0.000005 | | closing tables | 0.000005 | | freeing items | 0.000010 | | cleaning up | 0.000010 | +----------------------+----------+ 2. Performance Schema -- 查看执行引擎调用存储引擎的次数 SELECT * FROM performance_schema.events_statements_summary_by_digest WHERE DIGEST_TEXT LIKE 'SELECT%users%' LIMIT 1\G -- 关键指标 COUNT_STAR : 10000 -- 执行次数 SUM_ROWS_EXAMINED : 100000 -- 扫描行数 SUM_ROWS_SENT : 10000 -- 返回行数 执行器性能影响 1. 扫描行数 -- 全表扫描(扫描100万行) SELECT * FROM users; -- 执行器调用100万次 rnd_next() -- 索引查询(扫描1行) SELECT * FROM users WHERE id = 1; -- 执行器调用1次 index_read() -- 优化:减少扫描行数 2. 回表次数 -- 非覆盖索引(需要回表) SELECT * FROM users WHERE name = 'Alice'; -- 1. 索引扫描:index_read(name='Alice') -- 2. 回表:read_row(主键=1) -- 覆盖索引(无需回表) SELECT id, name FROM users WHERE name = 'Alice'; -- 1. 索引扫描:index_read(name='Alice') -- 2. 直接返回(无需回表) -- 优化:使用覆盖索引 实战建议 1. 减少扫描行数 -- ❌ 不好:全表扫描 SELECT * FROM users WHERE age > 25; -- ✅ 好:使用索引 CREATE INDEX idx_age ON users(age); SELECT * FROM users WHERE age > 25; 2. 使用覆盖索引 -- ❌ 不好:需要回表 SELECT * FROM users WHERE name = 'Alice'; -- ✅ 好:覆盖索引 CREATE INDEX idx_name_age ON users(name, age); SELECT name, age FROM users WHERE name = 'Alice'; 3. 避免大结果集 -- ❌ 不好:返回100万行 SELECT * FROM users; -- ✅ 好:分页查询 SELECT * FROM users LIMIT 0, 100; 常见面试题 Q1: 执行引擎的作用是什么? ...

2025-01-15 · maneng

如约数科科技工作室

浙ICP备2025203501号

👀 本站总访问量 ...| 👤 访客数 ...| 📅 今日访问 ...