场景以及八股复习篇
文章目录
- MySQL
- **1. MVCC(多版本并发控制)**
- **2. 小表驱动大表,哪个表建索引?**
- **3. 索引失效场景**
- **4. 排序索引优化**
- **5. EXPLAIN有效字段**
- **6. 视图(View)**
- **7. 触发器(Trigger)**
- **8. 存储过程与函数**
- **9. MySQL主从复制及延迟处理**
- **10. ACID实现**
- **11. 数据库备份与恢复**
- **12. 死锁及处理**
- **13. 分区表**
- **14. 优化器使用**
- **15. JOIN与子查询性能比较**
- **16. 二进制日志(binlog)**
- **17. 幻读及解决**
- RabbitMQ
- **1. 消息队列中的消息可靠性保证**
- **(1)生产端可靠性**
- **(2)MQ 服务端可靠性**
- **(3)消费端可靠性**
- **(4)异常处理与监控**
- **2. Virtual Host(虚拟主机)作用**
- **定义**
- **核心作用**
- **使用场景**
- **3. 消息确认模式**
- **自动确认(Auto Ack)**
- **手动确认(Manual Ack)**
- **选择建议**
- **4. 消费者组(Consumer Group)**
- **定义**
- **核心机制**
- **注意事项**
- **总结**
- Nginx
- Nginx主进程如何重新加载配置文件
- Maven
- **1. Clean 生命周期**
- **2. Default 生命周期(Build 生命周期)**
- **3. Site 生命周期**
- **总结对比**
- **补充说明**
- JVM
- **JVM 组件有哪些?**
- **什么是逃逸分析(Escape Analysis)?**
- **对象逃逸的三种状态**:
- **逃逸分析的价值**:
- **触发条件**:
- **示例**:
- **对永久代(PermGen)和元空间(Metaspace)的看法**
- **永久代(PermGen)**(JDK 8 之前):
- **元空间(Metaspace)**(JDK 8+):
- **迁移建议**:
- **JIT 是什么?**
- **核心功能**:
- **JIT 分类**:
- **JIT 优化示例**:
- **如何监控 JVM 性能?**
- **常用工具和命令**:
- **监控指标**:
- **JVM 如何实现线程同步?**
- **核心机制**:
- **生产者-消费者模式示例**:
- **死锁检测的命令**
- **常用命令**:
- **预防死锁**:
- **主流的垃圾回收器和算法**
- **垃圾回收算法**:
- **主流垃圾回收器**:
- **选择建议**:
- JUC
- **你对 Executor 框架的了解**
- **你对 JUC 的了解**
- **谈谈 Callable 和 Future 用法**
- **CountDownLatch 应用举例**
- **CyclicBarrier 应用举例**
- **Atomic 常见使用**
- **阻塞队列有哪些实现?你了解 AQS 吗?谈一谈**
- **阻塞队列实现**:
- **AQS(AbstractQueuedSynchronizer)**
- **ConcurrentHashMap 和 Hashtable 区别**
MySQL
1. MVCC(多版本并发控制)
实现原理:
- Undo Log:记录数据修改前的版本,形成版本链。每次修改生成新版本,旧版本通过指针连接。
- Read View:事务执行快照读时生成的视图,包含活跃事务ID列表(
m_ids
)、最小事务ID(min_trx_id
)、最大事务ID(max_trx_id
)和当前事务ID(creator_trx_id
)。 - 可见性判断:通过对比记录的事务ID(
DB_TRX_ID
)和Read View,决定版本是否可见。
解决的问题:
- 脏读:只能读已提交的数据版本。
- 不可重复读:同一事务内多次读取结果一致(基于相同Read View)。
- 幻读:通过间隙锁(Gap Lock)和版本链防止。
2. 小表驱动大表,哪个表建索引?
- 小表驱动大表:将小表作为驱动表(外层循环),大表作为被驱动表(内层循环)。
- 索引建议:
- 大表的连接字段:在大表的连接列上建立索引,减少扫描量。
- 小表的过滤字段:如果小表有筛选条件(如WHERE),可在小表的过滤列建索引。
- 示例:
SELECT * FROM small_table s JOIN large_table l ON s.id = l.small_id;
- 在
large_table.small_id
上建索引。
- 在
3. 索引失效场景
场景 | 原因 | 示例 |
---|---|---|
函数操作 | 索引列被函数处理,无法匹配 | WHERE YEAR(create_time) = 2024 |
OR 条件 | 若OR条件涉及非索引列,索引失效 | WHERE id = 1 OR name = 'A' |
!= / NOT IN | 跳过索引,全表扫描 | WHERE status != 1 |
模糊匹配 | 左模糊(%abc )无法使用索引 | WHERE name LIKE '%abc' |
类型转换 | 隐式转换导致索引失效 | WHERE id = '123' (id为int) |
4. 排序索引优化
- 原则:
- 排序字段与索引列顺序一致:
ORDER BY a, b
→ 索引(a, b)
。 - 避免混合升序/降序:索引顺序需与排序方向一致。
- 覆盖索引:排序字段和查询字段都在同一索引中。
- 排序字段与索引列顺序一致:
- 示例:
CREATE INDEX idx_name_age ON users(name, age); SELECT name, age FROM users ORDER BY name, age; -- 使用索引
5. EXPLAIN有效字段
字段 | 含义 | 优化建议 |
---|---|---|
type | 访问类型(ALL < index < range < ref < eq_ref < const) | 优先使用 ref /eq_ref |
key | 实际使用的索引 | 检查是否命中预期索引 |
rows | 预计扫描行数 | 越小越好 |
Extra | 额外信息 | Using filesort (需优化排序)、Using temporary (需优化分组) |
6. 视图(View)
定义:虚拟表,基于SQL查询结果。
优点:
- 简化复杂查询:隐藏复杂逻辑。
- 数据安全:限制用户访问敏感数据。
- 逻辑独立性:表结构变更不影响视图。
缺点: - 性能开销:视图可能引发全表扫描。
- 更新限制:仅支持简单视图的更新(无聚合函数、DISTINCT等)。
7. 触发器(Trigger)
作用:在特定事件(INSERT/UPDATE/DELETE)发生时自动执行。
语法示例:
CREATE TRIGGER before_insert_user
BEFORE INSERT ON users
FOR EACH ROW
BEGINSET NEW.create_time = NOW();
END;
使用场景:
- 数据校验(如自动填充时间戳)。
- 审计日志(记录操作历史)。
- 注意事项:避免复杂逻辑,防止触发器嵌套导致性能问题。
8. 存储过程与函数
存储过程:
- 可包含多个SQL语句,支持输入/输出参数。
- 用于封装业务逻辑(如批量操作)。
函数: - 必须返回单个值,可在SQL语句中直接调用。
- 用于计算(如
SELECT my_function()
)。
区别:
| 特性 | 存储过程 | 函数 |
|------|----------|------|
| 返回值 | 多个参数(IN/OUT) | 单个返回值 |
| 调用方式 |CALL proc()
|SELECT func()
|
| 事务支持 | 支持 | 不支持 |
9. MySQL主从复制及延迟处理
实现步骤:
- 主库开启binlog:配置
server-id
和log-bin
。 - 从库配置:设置
server-id
并连接主库。 - 启动复制线程:
START SLAVE;
。
延迟处理:
- 优化主库负载:减少大事务和锁竞争。
- 调整同步方式:异步→半同步(
semi-sync
)减少延迟。 - 监控延迟:
SHOW SLAVE STATUS\G
中的Seconds_Behind_Master
。
10. ACID实现
- 原子性(Undo Log):通过回滚日志实现事务回滚。
- 一致性(约束+事务):通过约束(主键、外键)和事务机制保证。
- 隔离性(锁/MVCC):InnoDB通过行锁和MVCC实现。
- 持久性(Redo Log):通过重做日志将数据刷盘。
11. 数据库备份与恢复
备份方式:
- 物理备份(如
xtrabackup
):直接复制数据文件,速度快。 - 逻辑备份(如
mysqldump
):导出SQL语句,灵活但慢。
恢复步骤:
- 物理备份恢复:停止MySQL,替换数据文件。
- 逻辑备份恢复:导入SQL文件(
mysql -u root < backup.sql
)。
12. 死锁及处理
死锁条件:
- 互斥
- 请求与保持
- 不可剥夺
- 循环等待
预防方法:
- 按固定顺序访问资源。
- 设置超时(
innodb_lock_wait_timeout
)。
处理: - 查看死锁日志:
SHOW ENGINE INNODB STATUS\G
。 - 手动终止事务:
KILL <thread_id>;
。
13. 分区表
实现方式:
- 范围分区:按数值范围划分(如日期)。
- 列表分区:按枚举值划分。
- 哈希分区:按哈希函数值划分。
- 键分区:类似哈希,但由MySQL自动管理。
示例:
CREATE TABLE sales (id INT,sale_date DATE
)
PARTITION BY RANGE (YEAR(sale_date)) (PARTITION p2023 VALUES LESS THAN (2024),PARTITION p2024 VALUES LESS THAN (2025)
);
14. 优化器使用
- 统计信息:
ANALYZE TABLE
更新表统计信息,帮助优化器选择最优执行计划。 - 索引建议:通过
EXPLAIN
分析,添加缺失的索引。 - 查询重写:避免隐式转换、减少子查询嵌套。
15. JOIN与子查询性能比较
- JOIN:通常更快,尤其是当有合适索引时。
SELECT u.name, o.order_id FROM users u JOIN orders o ON u.id = o.user_id;
- 子查询:适合嵌套查询,但可能导致多次扫描。
SELECT name FROM users WHERE id IN (SELECT user_id FROM orders);
建议:优先使用JOIN,尤其在大数据量场景。
16. 二进制日志(binlog)
作用:
- 数据恢复:通过日志重放恢复误删数据。
- 主从复制:从库应用主库的binlog事件。
- 审计:记录所有数据库变更。
格式: - STATEMENT:记录SQL语句。
- ROW:记录行级变更。
- MIXED:混合模式。
17. 幻读及解决
定义:事务中两次查询结果集数量不一致(新增/删除记录)。
解决方案:
- 可重复读(RR)隔离级别:InnoDB通过间隙锁(Gap Lock)防止幻读。
- 串行化(Serializable):完全锁表,但性能差。
- 显式锁:使用
SELECT ... FOR UPDATE
锁定范围。
RabbitMQ
1. 消息队列中的消息可靠性保证
在 RabbitMQ 中,消息可靠性保证涉及 生产端、MQ 服务端 和 消费端 的协同配合,核心机制包括:
(1)生产端可靠性
-
消息持久化:
- 发送消息时,设置
deliveryMode=2
(持久化),确保消息写入磁盘而非仅内存。 - 队列需声明为持久化(
durable=true
),防止队列在 RabbitMQ 重启时丢失。 - 示例:
// 声明持久化队列 channel.queueDeclare("my_queue", true, false, false, null); // 发送持久化消息 AMQP.BasicProperties props = new AMQP.BasicProperties.Builder().deliveryMode(2).build(); channel.basicPublish("", "my_queue", props, "Hello, RabbitMQ!".getBytes());
- 发送消息时,设置
-
生产者确认(Confirm)机制:
- 开启
publisher_confirm
,确保消息成功写入队列后才返回 ACK。 - 若未收到 ACK,生产者可重试发送消息(需配合幂等性设计)。
- 示例:
channel.confirmSelect(); // 启用确认模式 if (!channel.waitForConfirms()) {// 重试发送消息 }
- 开启
(2)MQ 服务端可靠性
-
持久化存储:
- 默认情况下,消息存储在内存中,重启后丢失。必须配置磁盘持久化(如 Kafka 的日志文件)。
- RabbitMQ 支持 Lazy Queues(惰性队列),消息直接落盘,避免内存积压导致 PageOut 阻塞。
-
高可用架构:
- 镜像队列:通过集群部署,将队列复制到多个节点,防止单点故障。
- 集群部署:结合主从节点和自动故障转移(如 Kafka 的 ISR 列表),确保数据冗余和可用性。
(3)消费端可靠性
-
手动确认(ACK)机制:
- 消费者处理完消息后,手动发送
basic.ack
,确保消息不会被 RabbitMQ 过早删除。 - 若消费者处理失败(如宕机),消息会被重新投递到其他消费者。
- 示例:
DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");try {processMessage(message); // 处理消息channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); // 手动确认} catch (Exception e) {// 记录日志或重试,不确认消息} }; channel.basicConsume("my_queue", false, deliverCallback, consumerTag -> {});
- 消费者处理完消息后,手动发送
-
幂等性设计:
- 消费者需确保重复消费不影响业务逻辑(如数据库唯一约束、去重 ID)。
- 示例:
-- 数据库插入时通过唯一键避免重复 INSERT INTO orders (id, user_id, product) VALUES (?, ?, ?) ON CONFLICT (id) DO NOTHING;
(4)异常处理与监控
- 重试机制:
- 生产端发送失败时,通过重试策略(如指数退避)重新发送消息。
- 监控告警:
- 使用 Prometheus + Grafana 监控消息堆积、确认延迟等指标,及时发现异常。
2. Virtual Host(虚拟主机)作用
定义
Virtual Host 是 RabbitMQ 中的逻辑隔离单元,用于将不同的应用或用户资源(队列、交换机、绑定关系)隔离开,避免命名冲突和权限泄露。
核心作用
- 资源隔离
- 不同 vhost 的队列和交换机互不干扰,适合多租户场景(如生产环境 vs 测试环境)。
- 权限管理
- 用户权限绑定到特定 vhost,限制其只能访问所属 vhost 的资源。
- 示例:
# 创建 vhost rabbitmqctl add_vhost /production # 创建用户并绑定权限 rabbitmqctl add_user dev_user password rabbitmqctl set_permissions -p /production dev_user ".*" ".*" ".*"
- 灵活配置
- 每个 vhost 可独立配置参数(如消息 TTL、死信队列策略)。
使用场景
- 多项目隔离:不同业务系统使用独立 vhost,避免资源冲突。
- 环境隔离:开发、测试、生产环境分别使用不同 vhost,防止误操作。
- 租户管理:SaaS 应用中,为每个租户分配独立 vhost,保障数据安全。
3. 消息确认模式
自动确认(Auto Ack)
- 特点:
- 消息投递给消费者后立即标记为已确认,无需消费者显式确认。
- 优点:低延迟,适合快速处理且容忍少量消息丢失的场景。
- 缺点:若消费者处理失败(如宕机),消息会丢失。
- 适用场景:日志收集、实时性要求高的非关键业务。
手动确认(Manual Ack)
- 特点:
- 消费者需显式调用
basic.ack
确认消息处理完成。 - 优点:确保消息不丢失,适合关键业务(如订单支付)。
- 缺点:实现复杂,需处理异常重试。
- 消费者需显式调用
- 关键配置:
// 关闭自动确认 channel.basicConsume("my_queue", false, deliverCallback, consumerTag -> {});
选择建议
- 优先使用手动确认:保障消息可靠性,尤其在消费逻辑复杂或处理时间较长时。
- 自动确认的替代方案:若使用自动确认,需确保消费者逻辑健壮(如快速处理、无宕机风险)。
4. 消费者组(Consumer Group)
定义
消费者组是指多个消费者共享同一个队列,通过负载均衡分担消息处理任务。RabbitMQ 通过 轮询调度 将消息分发给组内消费者。
核心机制
-
负载均衡:
- RabbitMQ 按轮询方式将消息分发给在线消费者,避免单点过载。
- 示例:
// 定义两个消费者监听同一队列 @Component @RabbitListener(queues = "shared_queue") public class ConsumerA {@RabbitHandlerpublic void receive(String message) {System.out.println("ConsumerA: " + message);} }@Component @RabbitListener(queues = "shared_queue") public class ConsumerB {@RabbitHandlerpublic void receive(String message) {System.out.println("ConsumerB: " + message);} }
-
高可用性:
- 若某个消费者宕机,消息会被重新投递给其他消费者,确保任务不丢失。
- 需配合 手动确认 和 消息重试机制,防止消息因处理失败而丢失。
-
扩展性:
- 动态增加消费者实例,提升消息处理能力,适应流量高峰。
注意事项
- 避免重复消费:
- 手动确认需确保消费者在处理完消息后才发送
basic.ack
,否则消息可能被重复投递。
- 手动确认需确保消费者在处理完消息后才发送
- 幂等性设计:
- 即使消息重复投递,消费者应能识别并忽略重复请求(如通过唯一 ID 校验)。
总结
模块 | 关键点 |
---|---|
消息可靠性 | 持久化(队列/消息)、生产者确认、消费者手动确认、集群镜像、幂等性设计。 |
Virtual Host | 资源隔离、权限管理、环境隔离、租户管理。 |
消息确认模式 | 自动确认(低延迟) vs 手动确认(高可靠性),需结合业务场景选择。 |
消费者组 | 负载均衡、高可用、动态扩展,需配合手动确认和幂等性保障可靠性。 |
Nginx
Nginx主进程如何重新加载配置文件
SIGHUP
Maven
Maven 的三种生命周期(Clean、Default、Site)分别负责项目构建的不同阶段,以下是它们的详细作用和场景:
1. Clean 生命周期
作用:清理项目,删除之前的构建产物,确保构建环境的整洁。
- 核心阶段:
pre-clean
:在清理前执行,通常用于执行自定义的清理前操作(如备份配置文件)。clean
:删除目标目录(如target
)中的构建输出文件,确保后续构建从干净的状态开始。post-clean
:在清理后执行,通常用于执行清理后的操作(如恢复备份文件)。
典型使用场景:
- 项目初次构建前:避免旧版本文件干扰。
- 构建失败后重新尝试:清除可能存在的错误产物。
- 切换分支或环境时:确保不同环境的构建隔离。
示例命令:
mvn clean # 执行 clean 生命周期的 clean 阶段
mvn clean package # 先清理,再执行 package 阶段
2. Default 生命周期(Build 生命周期)
作用:构建项目,从源代码编译到打包、测试、安装和部署。
- 核心阶段(部分关键阶段):
validate
:验证项目配置(如pom.xml
是否正确)。compile
:编译主源代码(src/main/java
)。test
:运行单元测试(src/test/java
)。package
:将编译后的代码打包(如 JAR、WAR)。verify
:验证打包结果是否符合质量标准。install
:将包安装到本地 Maven 仓库(~/.m2/repository
),供其他项目使用。deploy
:将包部署到远程仓库(如 Nexus),供团队共享。
典型使用场景:
- 日常开发:
mvn compile
或mvn test
用于局部构建和测试。 - 打包部署:
mvn package
或mvn install
用于生成可分发的包。 - 发布版本:
mvn deploy
将最终包推送到远程仓库。
示例命令:
mvn compile # 编译主代码
mvn test # 编译并运行测试
mvn package # 编译、测试并打包
mvn install # 安装到本地仓库
mvn deploy # 部署到远程仓库
3. Site 生命周期
作用:生成项目文档和站点信息,用于项目报告和团队协作。
- 核心阶段:
pre-site
:生成文档前的准备操作。site
:生成项目站点文档(如 API 文档、测试报告、项目统计信息)。post-site
:生成文档后的处理(如自定义脚本)。site-deploy
:将文档部署到远程服务器(如 GitHub Pages、Nexus)。
典型使用场景:
- 项目文档生成:通过
mvn site
生成 HTML 格式的 API 文档、测试覆盖率报告等。 - 团队协作:使用
mvn site-deploy
将文档发布到团队内部服务器或公共网站。
示例命令:
mvn site # 生成项目文档
mvn site-deploy # 将文档部署到远程服务器
总结对比
生命周期 | 核心目的 | 典型命令 | 适用场景 |
---|---|---|---|
Clean | 清理构建产物 | mvn clean | 重新构建前、切换环境 |
Default | 构建、测试、打包、部署 | mvn compile , mvn deploy | 日常开发、版本发布 |
Site | 生成和部署项目文档 | mvn site , mvn site-deploy | 项目文档维护、团队协作 |
补充说明
- 生命周期的独立性:三种生命周期相互独立,互不干扰。例如,
mvn clean
只执行 Clean 生命周期,不会影响 Default 或 Site。 - 插件绑定:每个生命周期的阶段通常绑定特定插件(如
maven-compiler-plugin
绑定到compile
阶段),用户可通过自定义插件扩展功能。 - 命令组合:Maven 支持组合命令,如
mvn clean package
会依次执行 Clean 生命周期的clean
阶段和 Default 生命周期的package
阶段。
JVM
JVM 组件有哪些?
JVM(Java Virtual Machine)的核心组件包括以下部分:
-
类加载子系统(Class Loader System)
- 作用:负责将
.class
文件加载到 JVM 中,并验证、解析和初始化类。 - 关键流程:
- 加载:从文件系统、网络或内存中读取类的二进制数据。
- 验证:确保类符合 JVM 规范(如字节码校验)。
- 准备:为类变量(
static
变量)分配内存并设置默认值。 - 解析:将符号引用转换为直接引用(如方法地址)。
- 初始化:执行类构造器
<clinit>
方法(静态代码块和静态变量赋值)。
- 作用:负责将
-
运行时数据区(Runtime Data Areas)
- 程序计数器(Program Counter Register)
- 线程私有,记录当前线程执行的字节码指令地址。
- Java 虚拟机栈(Java Virtual Machine Stacks)
- 线程私有,存储局部变量、操作数栈、动态链接、方法返回地址等。
- 每个方法调用会创建一个栈帧(Stack Frame)。
- 本地方法栈(Native Method Stack)
- 用于执行 Native 方法(如 JNI 调用)。
- 堆(Heap)
- 所有线程共享,存储对象实例和数组。
- 分代管理:新生代(Young Generation)、老年代(Old Generation)、元空间(Metaspace)。
- 方法区(Method Area)
- 存储类的元数据(如类信息、常量池、静态变量、编译器编译后的代码缓存)。
- 永久代(PermGen)(JDK 8 之前) vs 元空间(Metaspace)(JDK 8+)。
- 程序计数器(Program Counter Register)
-
执行引擎(Execution Engine)
- 作用:将字节码转换为本地机器指令执行。
- 核心功能:
- 解释器(Interpreter):逐行解释字节码,速度快但效率低。
- 即时编译器(JIT Compiler):将热点代码编译为本地机器码,优化性能。
- 垃圾回收器(Garbage Collector):管理堆内存,回收无用对象。
-
本地方法接口(Native Interface)
- 提供与本地库(如 C/C++)交互的接口(JNI)。
- 用于实现 JVM 内部无法直接完成的功能(如文件 I/O、多线程调度)。
什么是逃逸分析(Escape Analysis)?
逃逸分析是 JVM 在 即时编译(JIT)阶段 的一种高级优化技术,用于判断对象的作用域范围,是否“逃逸”出当前方法或线程。
对象逃逸的三种状态:
- 不逃逸(NoEscape):对象仅在当前方法内部使用。
- 优化:栈上分配(Stack Allocation)、标量替换(Scalar Replacement)。
- 方法逃逸(ArgEscape):对象作为参数传递给其他方法。
- 优化:同步消除(Synchronization Elimination)。
- 线程逃逸(GlobalEscape):对象被其他线程访问。
- 优化:几乎无法优化。
逃逸分析的价值:
- 栈上分配:避免堆内存分配压力,减少 GC 压力。
- 标量替换:将对象拆分为基本类型(如
Point x, y
拆分为int x, int y
)。 - 同步消除:移除不必要的锁操作(如
synchronized
)。
触发条件:
- 方法被频繁调用(热点代码)。
- 对象生命周期可准确分析。
- JVM 运行在 Server 模式(
-server
)。
示例:
public void process() {Point p = new Point(1, 2); // 不逃逸对象System.out.println(p.x + p.y);
}
JVM 可能将 p
栈上分配,无需堆内存分配。
对永久代(PermGen)和元空间(Metaspace)的看法
永久代(PermGen)(JDK 8 之前):
- 特点:
- 存在于堆中,大小有限(需手动配置
-XX:MaxPermSize
)。 - 垃圾回收效率低,容易导致
OutOfMemoryError: PermGen space
。
- 存在于堆中,大小有限(需手动配置
- 缺点:
- 配置复杂,内存管理不够灵活。
- 元数据(如类信息)和堆内存耦合,难以隔离。
元空间(Metaspace)(JDK 8+):
- 特点:
- 使用 本地内存(Native Memory),不受堆内存限制。
- 默认无上限,可通过
-XX:MaxMetaspaceSize
限制。
- 优势:
- 内存管理更灵活高效,减少 OOM 风险。
- 与堆解耦,便于隔离元数据和堆内存。
- 支持动态扩展,适应大规模类加载场景。
迁移建议:
- 推荐使用元空间:避免 PermGen 的配置复杂性和 OOM 问题。
- 监控元空间:使用
jcmd VM.native_memory
或jstat -gcmetacapacity
监控元空间占用。
JIT 是什么?
JIT(Just-In-Time Compiler) 是 JVM 的即时编译器,将热点字节码编译为本地机器码,提升执行效率。
核心功能:
- 热点代码识别:通过采样或计数器(如
InvokeCounter
)识别频繁执行的代码。 - 编译优化:
- 方法内联(Inlining):将小方法直接嵌入调用处。
- 寄存器分配(Register Allocation):优化寄存器使用,减少内存访问。
- 消除冗余代码:如常量折叠、死代码删除。
- 动态优化:根据运行时数据(如分支概率)调整编译策略。
JIT 分类:
- 经济 JIT(C1 编译器):快速编译,无复杂优化,适用于资源紧张环境。
- 普通 JIT(C2 编译器):全面优化,适合性能敏感场景(如服务端应用)。
JIT 优化示例:
public int sum(int a, int b) {return a + b; // 热点方法可能被内联
}
JIT 会将 sum
方法内联到调用处,减少方法调用开销。
如何监控 JVM 性能?
常用工具和命令:
-
jstat:统计 JVM 内存和 GC 情况。
- 示例:
jstat -gc <PID> 1000 5 # 每秒打印 GC 统计信息 5 次 jstat -gcutil <PID> 1000 5 # 查看 GC 利用率
- 示例:
-
jcmd:查看 JVM 状态和触发诊断操作。
- 示例:
jcmd <PID> VM.native_memory summary # 查看堆外内存使用 jcmd <PID> GC.class_histogram # 查看堆内存对象分布
- 示例:
-
jconsole/VisualVM:图形化监控内存、线程、GC 等。
-
Arthas(阿里开源):
- 功能:
- 实时查看线程状态(
thread
)。 - 分析内存泄漏(
monitor
)。 - 诊断死锁(
thread -b
)。
- 实时查看线程状态(
- 功能:
-
NMT(Native Memory Tracking):
- 启用:
-XX:NativeMemoryTracking=summary # 基础模式 -XX:NativeMemoryTracking=detail # 详细模式
- 查看:
jcmd <PID> VM.native_memory summary scale=MB
- 启用:
-
GC 日志分析:
- 启用参数:
-Xlog:gc*:file=gc.log:time:filecount=5,filesize=10M
- 分析工具:GCEasy、GCViewer、VisualVM。
- 启用参数:
监控指标:
- GC 停顿时间:控制在
MaxGCPauseMillis
(如 200ms)。 - 堆内存使用率:避免频繁 Full GC(老年代使用率 > 70%)。
- 线程状态:检查
BLOCKED
或WAITING
线程占比。
JVM 如何实现线程同步?
核心机制:
-
synchronized 关键字:
- 对象锁:对类实例加锁(
synchronized(this)
)。 - 类锁:对类加锁(
synchronized(ClassName.class)
)。 - 锁升级:偏向锁 → 轻量级锁 → 重量级锁(JDK 6+)。
- 对象锁:对类实例加锁(
-
Lock 接口(ReentrantLock):
- 显式锁:需手动
lock()
和unlock()
。 - 公平锁:按请求顺序获取锁(
ReentrantLock(true)
)。 - 条件变量:通过
Condition
实现线程间通信(替代wait/notify
)。
- 显式锁:需手动
-
volatile 关键字:
- 保证可见性:写操作对其他线程立即可见。
- 禁止指令重排序:通过内存屏障(Memory Barrier)实现。
-
Atomic 类(CAS 原理):
- 无锁操作:基于 CPU 原子指令(如
Compare And Swap
)。 - 典型实现:
AtomicInteger
、AtomicReference
。
- 无锁操作:基于 CPU 原子指令(如
生产者-消费者模式示例:
BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);
// 生产者
new Thread(() -> {while (true) {queue.put("data"); // 阻塞直到队列未满}
}).start();
// 消费者
new Thread(() -> {while (true) {String data = queue.take(); // 阻塞直到队列非空}
}).start();
死锁检测的命令
常用命令:
-
jstack:生成线程转储(Thread Dump),分析死锁。
- 示例:
jstack <PID> > thread_dump.txt
- 输出分析:
Found one Java-level deadlock: "Thread-1":waiting to lock monitor 0x00007f8c0000,which is held by "Thread-0" "Thread-0":waiting to lock monitor 0x00007f8c0001,which is held by "Thread-1"
- 示例:
-
Arthas:
- 命令:
thread -b # 查找死锁
- 命令:
-
jcmd:
- 示例:
jcmd <PID> Thread.print # 查看线程状态
- 示例:
预防死锁:
- 按固定顺序获取锁。
- 设置超时(
tryLock(timeout)
)。 - 减少锁粒度(如分段锁)。
主流的垃圾回收器和算法
垃圾回收算法:
-
标记-清除(Mark-Sweep)
- 流程:标记存活对象 → 清除无用对象。
- 缺点:产生内存碎片,可能导致大对象分配失败。
-
标记-整理(Mark-Compact)
- 流程:标记存活对象 → 整理至内存一端。
- 优点:避免内存碎片,适合老年代。
-
复制(Copying)
- 流程:将存活对象复制到另一块内存区域。
- 典型应用:新生代的 Eden 区和 Survivor 区。
-
分代收集(Generational Collection)
- 理论基础:
- 弱分代假说:大多数对象生命周期短。
- 强分代假说:熬过多次 GC 的对象更难消亡。
- 分代策略:
- 新生代(Young Gen):使用复制算法(Serial、ParNew)。
- 老年代(Old Gen):使用标记-整理或标记-清除(CMS、G1)。
- 理论基础:
主流垃圾回收器:
-
Serial GC(单线程,适用于单核机器)
- 特点:Stop-The-World,适合小型应用。
-
Parallel Scavenge GC(多线程,吞吐优先)
- 特点:并行回收,适合批处理任务。
-
CMS GC(并发标记清除,低延迟)
- 缺点:产生内存碎片,需配合
CMSInitiatingOccupancyFraction
。
- 缺点:产生内存碎片,需配合
-
G1 GC(Garbage-First,适用于大堆内存)
- 特点:分区回收(Region),平衡吞吐和延迟。
- 关键参数:
-XX:+UseG1GC # 启用 G1 -XX:MaxGCPauseMillis=200 # 控制最大停顿时间 -XX:G1HeapRegionSize=4M # 区域大小
-
ZGC/Shenandoah(低延迟,毫秒级停顿)
- 特点:染色指针(Colored Pointers)或 Load Barrier 技术。
选择建议:
- 吞吐优先:Parallel Scavenge + Serial Old。
- 低延迟:G1 或 ZGC(JDK 11+)。
- 大堆内存:G1 或 ZGC。
JUC
你对 Executor 框架的了解
Executor 框架是 Java 并发编程的核心工具,通过解耦任务的提交与执行,提供高效、灵活的多线程解决方案。其核心接口和实现包括:
Executor
- 最基本的接口,定义
execute(Runnable command)
方法,用于执行提交的任务。
- 最基本的接口,定义
ExecutorService
- 扩展
Executor
,提供更丰富的功能:- 提交任务(
submit(Runnable/Callable)
)。 - 关闭线程池(
shutdown()
、shutdownNow()
)。 - 管理任务生命周期(
isShutdown()
、awaitTermination()
)。
- 提交任务(
- 扩展
ScheduledExecutorService
- 支持定时或周期性任务(如
schedule()
、scheduleAtFixedRate()
)。
- 支持定时或周期性任务(如
线程池实现:
ThreadPoolExecutor
:核心线程池,支持自定义参数(核心线程数、最大线程数、任务队列等)。ScheduledThreadPoolExecutor
:支持定时任务的线程池。
优势:
- 资源管理:通过线程复用减少创建/销毁开销。
- 任务调度:支持优先级、延迟、周期性任务。
- 优雅关闭:通过
shutdown()
或shutdownNow()
安全终止线程池。
示例:
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> System.out.println("Task executed"));
executor.shutdown();
你对 JUC 的了解
JUC(java.util.concurrent
)是 Java 并发编程的核心包,提供以下组件:
- 线程池(Executor 框架):管理线程生命周期,提升资源利用率。
- 同步工具:
CountDownLatch
:等待多个线程完成。CyclicBarrier
:让线程互相等待到达屏障点。Semaphore
:控制资源访问数量。
- 原子类(
Atomic
系列):基于 CAS 实现无锁操作(如AtomicInteger
)。 - 并发集合:
ConcurrentHashMap
:线程安全的哈希表。CopyOnWriteArrayList
:写时复制的列表。
- 阻塞队列:
ArrayBlockingQueue
:有界队列。LinkedBlockingQueue
:无界或有界队列。SynchronousQueue
:无缓冲队列。
核心目标:
- 解决并发场景下的线程安全、资源竞争、死锁等问题。
- 提供高性能、可扩展的并发工具。
谈谈 Callable 和 Future 用法
Callable
- 返回结果的任务接口,定义
call()
方法(有返回值)。 - 与
Runnable
的区别:支持返回值和抛出异常。
- 返回结果的任务接口,定义
Future
- 表示异步计算的结果,提供以下方法:
get()
:获取任务结果(阻塞直到完成)。cancel()
:取消任务。isDone()
:判断任务是否完成。
- 表示异步计算的结果,提供以下方法:
使用示例:
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(() -> {return 42;
});
System.out.println(future.get()); // 输出 42
executor.shutdown();
CountDownLatch 应用举例
作用:让一个或多个线程等待其他线程完成操作。
典型场景:主线程等待多个子线程完成初始化。
示例:
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {new Thread(() -> {// 模拟子线程工作latch.countDown();}).start();
}
latch.await(); // 主线程等待
System.out.println("All threads completed");
CyclicBarrier 应用举例
作用:让一组线程互相等待,直到所有线程到达屏障点。
典型场景:多线程协同计算(如赛跑选手同时起跑)。
示例:
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("All arrived!"));
for (int i = 0; i < 3; i++) {new Thread(() -> {try {barrier.await(); // 等待其他线程} catch (Exception e) {}}).start();
}
Atomic 常见使用
核心原理:基于 CAS(Compare and Swap) 实现无锁操作。
常用类:
AtomicInteger
:原子整数操作。AtomicReference
:原子引用操作。AtomicLong
:原子长整型操作。
应用场景:
- 计数器:线程安全的递增/递减。
- 状态标志:无锁状态更新(如
AtomicBoolean
)。
示例:
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet(); // 原子递增
阻塞队列有哪些实现?你了解 AQS 吗?谈一谈
阻塞队列实现:
ArrayBlockingQueue
:有界队列,基于数组。LinkedBlockingQueue
:无界或有界队列,基于链表。PriorityBlockingQueue
:支持优先级排序的无界队列。SynchronousQueue
:无缓冲队列(直接传递)。DelayQueue
:元素需延迟到期后才能取出。
AQS(AbstractQueuedSynchronizer)
核心作用:为同步组件(如 ReentrantLock
、Semaphore
)提供基础实现。
关键特性:
- CLH 队列:维护等待线程的队列。
- 状态管理:通过
state
变量控制资源状态(如锁的持有)。 - 支持模式:
- 独占模式(如
ReentrantLock
):仅允许一个线程持有锁。 - 共享模式(如
CountDownLatch
):允许多个线程同时访问。
- 独占模式(如
实现原理:
- 线程尝试获取资源时,若失败则进入等待队列。
- 释放资源时,唤醒队列中的下一个线程。
ConcurrentHashMap 和 Hashtable 区别
特性 | ConcurrentHashMap | Hashtable |
---|---|---|
线程安全机制 | 分段锁(JDK 8 前)或 CAS+synchronized(JDK 8+) | 全局锁(synchronized 方法) |
性能 | 高(并发读写效率高) | 低(所有操作都加锁) |
迭代器 | 弱一致性(不会抛异常) | 强一致性(可能抛 ConcurrentModificationException ) |
是否支持 null | 不支持 | 支持 |
扩容机制 | 动态扩容(并发进行) | 需要手动扩容 |
适用场景:
ConcurrentHashMap
:高并发读写场景(如缓存)。Hashtable
:遗留代码或低并发场景。