目标岗位:物联网平台 / 交易支付后端 · 14 年 Java · 工业物联网专家
你简历:搭建高可用 MQTT Source/Sink,保证消息可靠性;MQTT + WebSocket 实现控制指令下发和前端实时监控;支撑 5000+ 测点,延迟 <1s。
物联网设备通信,MQTT 比 HTTP 优势主要在四点:
我们项目 5000+ 测点、很多是工业现场的 PLC/传感器,网络环境差,HTTP 撑不住。所以选了 MQTT。
面试官问的是你做技术选型的判断逻辑,不是"MQTT 是什么"。要体现"场景驱动选型",不能只背协议定义。HTTP 也能做 IoT(轮询),但代价大——面试官想看你是否真懂场景。
选 QoS1 的原因:
这是MQTT 必问题。回答要体现:不是"教科书答案",而是"在真实业务里怎么权衡"。面试官想看你对"可靠性 vs 性能"有没有自己的判断。
MQTT 和 Kafka 解决的问题不一样,分工合作:
没有 Kafka 时的问题:
架构:设备 → EMQX → Kafka → Flink → TDengine / 业务。Kafka 是"数据枢纽"。
这是架构师级别问题。考察你对"分层架构"的理解:MQTT 是"接入层",Kafka 是"传输层",Flink 是"计算层"。讲清楚职责分离。
我们用层级式 Topic,类似文件系统路径:
iot/{productKey}/{deviceId}/data、iot/{productKey}/{deviceId}/event、iot/{productKey}/{deviceId}/statusiot/{productKey}/{deviceId}/cmd、iot/{productKey}/{deviceId}/configiot/+/+/data 就能拿到所有设备数据。设计原则:
# 全订阅只能给内部管理用,给业务方会拖垮 Broker。Topic 设计是 MQTT 项目最直接的经验体现。面试官会从你的设计判断你是否做过大规模项目。乱设计的 topic 上线后无法扩展。
下发链路:业务系统 → 控制服务 → MQTT Broker → 设备。关键设计:
控制指令是 MQTT 项目里最容易出故障的部分。考察你对"分布式系统异步调用 + 状态机 + 幂等"的掌握。
设备离线分两个角度处理:
这是考察"对 MQTT 协议特性是否真的用起来"的题。Last Will 和 Retain Message 是 MQTT 的特色,但很多新手从来没用过。答出这两个词直接加分。
消息不丢要分 5 个节点全链路保障,任何一个断链都不行:
allow_anonymous,开启持久化会话(clean_session=false),客户端重连后能续传。acks=all + 副本 ≥3 + min.insync.replicas=2。任何一环都有兜底:设备缓存、Broker WAL、Kafka 副本、Consumer 重试。
这是面试"消息可靠性"的标准题。你的方案必须全链路 5 个节点都说一遍,只说"用 QoS1"是不够的。
MQTT QoS1 至少一次 + Kafka 至少一次,重复是必然,要靠业务层去重。三种去重策略:
deviceId + seq 或 deviceId + timestamp,落库前用 INSERT ... ON DUPLICATE KEY UPDATE 或唯一索引。SETNX messageId 1 EX 86400,存在就跳过。我们项目用 deviceId + timestamp 唯一键 + TDengine 唯一索引,因为数据量极大(百万级/天),Redis 存不下。
面试官知道"不丢也意味着可能重复",这题考察你幂等性的设计能力。和支付幂等(第四阶段)是同一套思路。
(deviceId, sessionId, seq) 三元组。开源 Broker 主流对比:
选 EMQX 的关键原因:
面试官看你是做过技术选型调研,还是"听说 EMQX 不错就用了"。对比表 + 国产化 + 插件是关键。
EMQX 官方压测:单节点 100w+ MQTT 连接、100w+ QPS 消息路由。但实际生产要看四个变量:
我们项目:5000 测点 × 1Hz × 平均 300 字节 ≈ 1.5MB/s 入站流量,4 核 8G 单节点完全够用,预留 10 倍容量。
但生产部署至少 3 节点集群做 HA,避免单点。
考察你对"理论 vs 实际"的判断。直接说"100w"显得没做过;说"看场景"才是工程师思维。
MQTT 认证三件套:
我们方案:设备首次激活时,烧录 productKey + deviceKey + 平台颁发的 deviceSecret(动态 token,24h 过期)。设备每次连接用 secret 签一次名,服务端校验。
EMQX 后端认证:MySQL/HTTP/Redis 插件。生产用 HTTP 自定义认证,调用我们的设备管理服务,灵活支持"禁用/限速"。
物联网安全是必问的,不能只回答"用密码",要展示完整的认证体系设计:设备身份、动态密钥、平台校验、吊销机制。
四个层面:
allow_anonymous。我们还做:连接频率限制、消息大小限制、topic 黑名单(防恶意订阅)。
工业物联网常被攻击(去年美国燃油管道勒索事件),安全是必问题。要展示"多层防御"思维。
KeepAlive 是 MQTT 连接的心跳机制:
我们项目设 60s:太短耗电+浪费流量;太长断线感知慢。
这是基础中的基础,但很多人只背协议不背参数。答出 1.5 倍这个细节直接说明真用过。
Retain(保留消息)是 MQTT 的一个特殊标志位:
retain=true,Broker 会把这条消息持久化在该 topic 上。retain=true + payload 为空 的消息可以清除该 topic 的保留消息。典型用法:
Retain Message 80% 的人背不下来具体用法,答出"立即拿到最新状态"就能让面试官眼前一亮。
iot/{productKey}/{deviceId}/status topic,监控大屏订阅这个 topic,重启后 1 秒内就能看到设备状态。"Last Will and Testament(遗嘱消息,LWT):
典型用法:设备掉线告警。设备连接时注册 Will 到 iot/{productKey}/{deviceId}/status,payload 是 {"online":false}。设备异常断网时,平台立即收到告警。
和 Q14 Retain 配合记忆:Retain 是"最新状态",Will 是"异常掉线告警"。这俩是 MQTT 协议特色,用好了能省一大堆代码。
你简历:MQTT 数据通过 Kafka 进入 Flink;要求理解 Kafka 核心原理才能讲清楚选型。
核心原因:解耦 + 削峰 + 广播 + 持久化。详细:
和 Q3 类似,但侧重 Kafka 自身价值。"削峰"是 Kafka 最经典的卖点,必答。
三个层面保证:
acks=all + 重试 + 幂等生产者(enable.idempotence=true,自动去重)。replication.factor=3)min.insync.replicas=2)log.flush.interval.messages 不依赖,主要靠 OS 页缓存 + 副本。enable.auto.commit=false),先处理业务再手动 commit;处理失败不进 commit,下次重读。最严苛配置:acks=all + replication.factor=3 + min.insync.replicas=2 + enable.idempotence=true。
这是 Kafka 面试第一高频题,也是 MQ 类问题的"三段式"标准答案:生产、Broker、消费三段全说。
replication.factor=3 就万无一失,其实如果 min.insync.replicas=1,leader 一写就 ack,副本还没同步就挂了照样丢。两个参数必须配合。"Kafka 高吞吐来自 5 个设计:
linger.ms + batch.size),减少网络请求数。考察"对底层原理的理解深度"。这 5 点都答出来就是 Kafka 高级;只答 2-3 点是初级。
FileChannel.transferTo(),底层调用 Linux 的 sendfile 系统调用。我们项目:Flink 是一个 group 消费所有数据;告警服务是另一个 group 也消费所有数据,互不影响。
Group 是 Kafka 设计的精髓。考察你是否真正理解 Kafka 的消费模型(和 MQ 的"队列模式 vs 发布订阅模式"异曲同工)。
__consumer_offsets 内部 topic 里(默认 50 个 partition,副本数 3)。三个关键点:
commitSync() / commitAsync())。auto.offset.reset=earliest|latest,新 group 没提交过 offset 时怎么选起点。Offset 是 Kafka 消费的"游标",理解它才能理解 Kafka 怎么实现"不丢不重"。
enable.auto.commit=false,每批处理完手动 commitSync,宁可慢一点也不丢消息。"Kafka 的顺序保证是分区级别的,不是全局有序:
想保证"某类业务有序",三种方案:
key=deviceId)。我们项目:deviceId 作为 Kafka key,保证同一设备消息严格有序。
考察"你对分布式系统顺序性的理解"。常见错误是"以为 Kafka 全局有序",需要纠正。
deviceId 作 key 后,单 partition 顺序消费,Flink 侧无需重排序,性能最优。"Kafka 削峰的原理是"以时间换空间":
实战配置:
max.poll.records 限速 + 监控 lag。削峰是 Kafka 的"招牌功能",要讲清楚"以时间换空间"和"lag 监控"两个关键点。
retention.ms)。核心区别:Kafka 是"日志型",RabbitMQ 是"消息队列型"。
选型逻辑:
我们项目 IoT 数据走 Kafka,业务支付走 RabbitMQ(你一卡通项目用的)。
这是"中间件选型"类问题,展示你对不同 MQ 的差异化理解。注意:你简历上两个项目分别用了两个 MQ,答出"按场景用"是加分项。
Rebalance 是 Consumer Group 内重新分配 partition的过程,触发时机:
session.timeout.ms(默认 10s)没发心跳。Rebalance 期间整个 group 停止消费(STW),对在线业务影响很大。
避免频繁 Rebalance:
session.timeout.ms(如 30s)和 max.poll.interval.ms(如 5min)。group.instance.id)让重启的 consumer 复用旧 ID,避免触发 rebalance。Rebalance 是 Kafka 消费侧的"老大难"。真实生产中 80% 的"消费慢"问题都和 rebalance 有关,懂这个就说明踩过坑。
rebalance-rate。你简历核心:45+ Flink 算子、毫秒级实时处理、单 Job 多 DAG 并行、DAG 热更新、GraalVM JS 沙箱。这是你最深的护城河。
Flink 在工业实时数据场景的核心优势:
我们项目 5000+ 测点、毫秒级延迟、复杂聚合计算,Flink 是唯一合适的选择。
这是 Flink 面试的开篇,必答"流批一体"和"事件时间"。但你简历的最大亮点是"自定义 DAG",这才是真正区分你和别人的点。
allowedLateness,超过的数据进侧输出流。核心区别:Spark 是批处理思维,Flink 是流处理思维。
选型逻辑:实时性要求高 + 自定义计算逻辑复杂 → Flink;离线报表 + 机器学习 → Spark。
我们项目是工业实时数据,Spark 不合适。
这是"大数据选型"经典问题。不要踩 Spark 抬 Flink,要客观对比,让面试官觉得你懂生态。
传统 Flink 架构:一个业务一个 Job,管理复杂,资源浪费。 我们做了单 Job 多 DAG设计:
代价:
但收益:支持用户拖拽式配置计算流程,业务方不再等开发。
这是你简历的核心亮点,必须讲透。面试官从这个问题判断你到底"用过 Flink"还是"用过 Flink 的 Hello World"。
用户在前端用X6 流程图画 DAG,后端解析成可执行 Flink 算子链。流程:
stream.transform() 或 ProcessFunction 把算子串成 DataStream 链。运行时:Flink 通过 BroadcastStream 广播 DAG 定义,TaskManager 按定义动态构建算子链。
这是你项目最技术的部分。从 JSON 到 Flink 算子的完整链路,展示你的全栈能力。
三种方案对比:
KeyedBroadcastProcessFunction 实现"广播状态 + 业务状态"双流处理。实际实现:
AbstractDagFunction)。这是你简历最值钱的点:"DAG 热更新"。答出 Broadcast State 立刻高级,答 Savepoint 只能是中级。
先说 Flink Window 是干嘛的:按时间或数量切片聚合(滚动、滑动、会话窗口)。
为什么我们大部分场景不用:
但 Window 在"离线统计报表"场景还是会用:TumblingEventTimeWindow 按小时/天聚合。
考察"你是否真正理解 Flink Window 的代价"。"为什么不用 Window"是反面问题,展示你深度思考过。
Checkpoint 是 Flink 的容错机制,核心是 Chandy-Lamport 分布式快照算法:
Checkpoint 是 Flink "四大核心"(事件时间、状态、Checkpoint、watermark)之一。必答 Barrier 概念。
送分题,必须全部答对。简历里你用 Redis 做了大量缓存/限流/分布式锁,这是验证基础的最佳机会。
Redis 官方压测:10w+ QPS。核心是 3 个"基于内存"的设计:
这是 Redis 第一必问题,90% 的面试从这开始。三个点都要答到。
这里"单线程"指命令执行是单线程(Redis 6 之前),原因:
补充:Redis 4.0 引入部分多线程(异步删除大 Key),Redis 6.0 才正式把网络 IO 多线程化。
考察"对单线程的真正理解"。很多人只说"快",不知道单线程反而是 Redis 的设计选择。
HGETALL 拉 1w 个字段,单请求 200ms,把整个 Redis 阻塞了 1 分钟。改成 HSCAN 迭代 + 拆分 Key 后恢复正常。"Redis 6 的多线程只用于网络 IO(读写 socket),命令执行仍然是单线程。原因:
配置:io-threads 4(一般 4-8 即可,开太多反而慢)。
这题考察"对 Redis 6 新特性的理解"。要明确"多线程 IO 不等于命令多线程"。
io-threads-do-reads yes + 线程数 > 1 才生效。Redis 五大基础 + 三大特殊:
SET/GET/MSET/MGET。适用:缓存、计数器、分布式锁(SETNX)。HSET/HGET/HGETALL。适用:对象存储(用户信息)、购物车。LPUSH/RPOP。适用:消息队列、最新列表(关注列表)。SADD/SMEMBERS。适用:标签、共同好友、UV 统计。ZADD/ZRANGE。适用:排行榜、延迟队列。三种特殊类型:
SETBIT/GETBIT。适用:用户签到、日活统计。PFADD/PFCOUNT。适用:UV 去重(12KB 统计 2^64 个数)。GEOADD/GEODIST。适用:附近的人。Redis 5+ 新增:Stream(消息队列,类 Kafka)。
基础题,但很少人能答全 8 种。尤其 Bitmap/HyperLogLog/GEO 是加分项。
定义:查询一个数据库里也不存在的数据,缓存永远没命中,请求全打到 DB。
常见场景:恶意攻击(请求 /user/-1 一定不存在)、查询无效 ID。
三种解决方案:
(key, null, EX 5min)。简单但占内存。Redis 经典三大问题(穿透/击穿/雪崩)的第一题。答出"BloomFilter"就是中高级水平。
BF.ADD / BF.EXISTS(RedisBloom 模块),或者用 Guava / Redisson 客户端。定义:某个热点 key 过期瞬间,大量请求同时打到 DB(雪崩是大量 key 同时过期,击穿是单 key)。
四种解决方案:
区分"击穿"vs"雪崩"是关键:击穿是 1 个 key,雪崩是 N 个 key 同时过期。要答出分布式锁方案。
SET key value NX EX 10(NX = 互斥、EX = 过期防死锁);解锁用 Lua 脚本保证原子性。tryLock + 超时;超时就降级返回旧值或默认值。定义:大量 key 同时过期,请求全打到 DB,DB 挂掉。常见原因:批量 key 设了相同过期时间。
四种解决方案:
EXPIRE = baseTime + random(0, 300s),避免同时过期。三件套(穿透/击穿/雪崩)必答全。和击穿区别要清晰。
定义:单个 key 对应的 value 过大。一般:
危害:
DEL 大 Key 是阻塞操作,UNLINK(异步删)才不阻塞。解决方案:
redis-cli --bigkeys、MEMORY USAGE key。UNLINK 或 SCAN + DEL 分批删。BigKey 是 Redis 性能问题的"头号杀手"。必答单线程阻塞风险。
redis-cli --bigkeys 扫一遍;生产用监控(redis_exporter)告警单 Key > 1MB。定义:某个 Key 被高频访问,导致 Redis 单节点压力过大(虽然不是 BigKey)。常见:明星离婚新闻、秒杀商品、热点微博。
危害:
解决方案:
product:detail:1 拆成 product:detail:1:1 ~ 1:8,8 个 Key 分散到不同 slot。HotKey 容易被忽略,和 BigKey 是不同维度的"高危 Key"。和 Q39 一起记忆。
redis-cli --hotkeys(需要 maxmemory-policy=allkeys-lfu);生产用 monitor 采样 + 统计。Redis 两种持久化方式:
save(阻塞)、bgsave(fork 子进程)。always(每条)、everysec(每秒,默认)、no(OS 控制)。必答基础。混合模式是 4.0+ 的新方案,体现你跟踪社区。
bgrewriteaof),合并重复命令,保留最新版本。PFORK 优化(Redis 6+)。数据库是 90% 面试必问,重点准备索引、事务、日志。
MySQL InnoDB 用 B+Tree 做索引,查询复杂度 O(logN),3-4 层就能存 2000w 数据:
这是 MySQL 索引的"灵魂问题",必答"矮胖"和"叶子节点链表"两点。
BTree 和 B+Tree 的核心区别:
B+Tree 优势:
考察"对索引底层原理的精确理解"。BTree 和 B+Tree 一字之差,差很多。
定义:索引和数据行物理上存在一起的索引就是聚簇索引。
在 InnoDB 中:
特点:
聚簇 vs 非聚簇是 MySQL 索引的核心概念。和"回表"是连体问题,必答。
定义:用二级索引查询时,先拿到主键,再回聚簇索引查全行数据,这个过程叫回表。
举例:表有 id(主键)、name、age。索引 idx_name,查 SELECT * FROM user WHERE name = '马雷':
idx_name → 拿到 id=100。如何避免回表:覆盖索引(详见 Q46)。如果查询字段都在索引里,就不需要回表。
回表是 MySQL 慢查询的"常见病因"。必答"避免回表 = 覆盖索引"。
Using index 表示覆盖索引,NULL 表示回表。SELECT * 才容易触发。SELECT * FROM order WHERE user_id=? 改成 SELECT id, amount FROM order WHERE user_id=?,走覆盖索引,QPS 提升 3 倍。"定义:查询的字段全部在索引里,不需要回表,就叫覆盖索引(Covering Index)。
举例:表有 id、name、age、address。建联合索引 (name, age):
SELECT id, name FROM user WHERE name = '马雷' → 走覆盖索引(id 是主键在叶子节点,name 在索引里)。SELECT id, name, age FROM user WHERE name = '马雷' → 走覆盖索引。SELECT * FROM user WHERE name = '马雷' → 需要 address → 回表。EXPLAIN 里 Extra: Using index 表示走了覆盖索引。
覆盖索引是 MySQL 性能优化的高频手段。和回表(Q45)是一对。
MVCC(Multi-Version Concurrency Control)多版本并发控制:同一行数据保留多个版本,让读不阻塞写、写不阻塞读。
InnoDB MVCC 实现核心:
trx_id(最近修改的事务 ID)和 roll_pointer(指向 Undo Log)。判断数据可见性:trx_id < ReadView.min_id(已提交可见)或 在活跃事务列表(不可见)或 >= max_id(不可见)。
MVCC 是 MySQL 高并发的核心。必答 "Undo Log + ReadView"。
MySQL 三种日志,作用不同:
innodb_flush_log_at_trx_commit)。两阶段提交:Redo Log 写完(prepare) → Binlog 写完 → Redo Log 标记 commit,保证两者一致。
这三种日志面试必问,必答两阶段提交。区分不清会让面试官怀疑你只是"用过 MySQL"而非"懂 MySQL"。
EXPLAIN SQL 查看 MySQL 怎么执行这条 SQL,优化必备工具。关键字段:
system > const > eq_ref > ref > range > index > ALL。至少要 ref,ALL 是全表扫描要优化。Using index:覆盖索引,好。Using where:用 where 过滤。Using filesort:需要优化,外部排序。Using temporary:需要优化,用了临时表。Explain 是 MySQL 优化的"金标准"工具。必答 type 字段的 7 个等级,否则就是没真正用过。
EXPLAIN ANALYZE(MySQL 8.0+)实际执行,给出真实数据。SQL 标准 4 个隔离级别,由低到高:
MySQL RR 级别能解决大部分幻读(MVCC + 间隙锁),所以一般不用 Serializable。
事务隔离级别是基础题。必答"脏读/不可重复读/幻读"三件套。
完整排查流程:
slow_query_log=ON + long_query_time=1(超 1s 记录)。mysqldumpslow 或 pt-query-digest(Percona 工具)找出最慢的 TOP SQL。ANALYZE TABLE 更新统计信息,避免选错索引。SELECT *、NOT IN、!=、函数操作索引字段。慢 SQL 排查是"日常基本功"。必答慢查询日志 + EXPLAIN,缺一不可。
analyze table 重新收集。SELECT * FROM t WHERE id > last_id LIMIT 10 替代 OFFSET 1000000。高级 Java 必问,内存模型、GC、调优是核心。
JDK 8 之后(最常用版本)的内存划分:
这是 JVM 第一必问题,5 个区域 + 堆分代必答全。
G1(Garbage First)是JDK 9+ 默认的垃圾回收器,目标是"大内存 + 低延迟"。
核心设计:
-XX:MaxGCPauseMillis=200,G1 按价值(垃圾多少)优先回收。GC 类型:
① Young GC:回收 Eden + Survivor。
② Mixed GC:回收部分老年代。
③ Full GC:单线程回收,要避免。
G1 是"现代 GC"必问。必答 Region + 可预测停顿。
-Xmx8g -XX:+UseG1GC -XX:MaxGCPauseMillis=200,平均停顿 80ms,最大 180ms。"Full GC 是 Java 应用最大杀手,停顿秒级甚至分钟。排查流程:
jstat -gcutil <pid> 1000 每秒打印 GC 状态,看 O(老年代)是否一直 100%。-Xlog:gc*:file=gc.log:time,分析 GC 频率和耗时。jmap -dump:format=b,file=heap.hprof <pid>(生产慎用,会触发 Full GC)。static Map)。Full GC 排查是面试"经典场景题"。必答"jstat + jmap + MAT"三件套。
jmap -F 强制模式(不一定成功)。dashboard 看线程 + 内存,thread 找死循环,watch 监控方法参数。ThreadLocal 里的大 Map 没清理。改成 try-finally 显式 remove 后再没复发。"定义:类加载时,先委托父加载器加载,父加载器加载不了才自己加载。
JDK 三层类加载器:
%JAVA_HOME%/lib(rt.jar、tools.jar),C++ 实现,最顶层。%JAVA_HOME%/lib/ext 或 java.ext.dirs。classpath 上的类,用户写的代码。流程:AppClassLoader → ExtClassLoader → Bootstrap,逐层向上问,找到就返回。
目的:
java.lang.*)不会被自定义类覆盖。类加载的必问基础,必答三层 + 流程 + 目的。
loadClass,不调用 parent。类从加载到卸载分 7 个阶段:
<clinit> 方法(static 变量赋值 + static 块)。注意:加载 ≠ 初始化。Class.forName 默认会初始化;ClassLoader.loadClass 不会。
类加载过程是 JVM 基础。必答"加载/验证/准备/解析/初始化"5 步。
<clinit> 怎么保证线程安全?" → JVM 加锁,同一个 ClassLoader 只执行一次。public static int x = 1,准备阶段 x=0,初始化阶段才 x=1。这是常考点。"OOM(OutOfMemoryError)分类:
java.lang.OutOfMemoryError: Java heap space → 对象太多,最常见。OutOfMemoryError: Metaspace → 类加载过多(动态代理、反射)。StackOverflowError → 递归太深。Direct buffer memory → NIO ByteBuffer 没释放。GC overhead limit exceeded → 98% 时间 GC,回收不到 2% 内存。排查流程:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heap.hprof 自动 dump。OOM 排查是面试必问场景。必答 5 种 OOM 类型 + 排查流程。
jmap -F 强制 dump;或者只看 Leaking Class(MAT 支持)。heapdump 命令在线 dump。Arthas 是阿里开源的Java 在线诊断工具,无需重启 JVM。常用命令:
thread -b 找死锁线程,thread -n 3 CPU 最高的 3 个线程。watch com.xx.Service method '{params, returnObj, throwExp}'。trace com.xx.Service method '#cost>10'(只打印耗时 > 10ms 的调用)。ognl '@com.xx.Config@getConfig()' 读静态字段。redefine /tmp/MyClass.class。生产救命神器。Arthas 是"生产排查神器",必答 dashboard/thread/watch/trace 四大命令,再补一个 redefine 加分。
MAT(Eclipse Memory Analyzer)是dump 文件分析工具,OOM 排查标配。核心功能:
SELECT * FROM com.xx.User WHERE name.length > 10。典型排查:
HashMap$Node[] 占用 60%。static Map 持有。MAT 是 OOM 排查的"瑞士军刀"。必答 Leak Suspects + Dominator Tree。
mat/ParseHeapDump.sh 命令行生成报告,不用 GUI。JVM 调优的核心思路:先诊断,再调参,再验证。常见场景:
-Xmx 调大。-XX:MaxMetaspaceSize 调大。-XX:MaxGCPauseMillis=200 设目标停顿。-Xms4g -Xmx4g(堆大小)。-Xlog:gc*:file=gc.log:time(GC 日志)。-XX:+HeapDumpOnOutOfMemoryError(OOM 自动 dump)。-XX:+UseG1GC(用 G1)。调优是"经验型问题",展示你"真的会做"而不是"背参数"。
-XX:InitiatingHeapOccupancyPercent=45,让 Mixed GC 更早触发,避免并发失败。四种基础 GC 算法:
GC 算法是理论,必答分代思想(为什么新生代用复制、老年代用标记-清除/整理)。
高级 Java 的"分水岭",synchronized、volatile、CAS、AQS 必问。
synchronized 是 Java 内置的互斥锁,JDK 1.6 后做了大量优化(锁升级)。
锁对象:
this。Class 对象。锁升级过程(重点):
底层:对象头的 Mark Word 存锁状态,monitor 对象 实现同步。
synchronized 是 Java 并发"基本功"。必答锁升级(无锁→偏向→轻量→重量)。
wait 释放锁并阻塞;notify 唤醒。Vector 替换为 ConcurrentHashMap,用 synchronized 的代码改成 ReentrantLock,QPS 提升 3 倍。"volatile 是 Java 的轻量级同步机制,保证:
底层原理:
MESI 缓存一致性协议)。和 synchronized 区别:
i++ 三步操作还是会出问题)。volatile 是必问基础,必答"可见性 + 禁止重排序"。
new 三步:分配内存 → 初始化 → 赋值引用,重排序后可能返回半初始化对象。CAS(Compare-And-Swap):比较并交换,乐观锁的基础。底层是 CPU 指令 cmpxchg。
三个操作数:内存地址 V、期望值 A、新值 B。当 V == A 时把 V 改成 B,否则什么都不做。
Java 中通过 Unsafe 类调用,封装在 AtomicInteger 等原子类中:
// AtomicInteger.incrementAndGet
do {
old = this.value;
new = old + 1;
} while (!compareAndSwap(value, old, new));
三大问题:
AtomicStampedReference)。CAS 是 Java 并发包的"灵魂",所有锁的底层都靠 CAS。必答 ABA 问题。
AQS(AbstractQueuedSynchronizer):Java 并发包的"地基",ReentrantLock / CountDownLatch / Semaphore 都基于它。
核心三要素:
volatile int 状态变量,子类自定义含义(ReentrantLock 是重入次数,Semaphore 是剩余许可)。compareAndSetState 改状态;失败就进队列阻塞。工作流程:
两种模式:独占锁(ReentrantLock)和共享锁(CountDownLatch)。
AQS 是"高级 Java 必问"。必答 state + CLH 队列 + CAS三件套。
ReentrantLock 是基于 AQS 的可重入互斥锁,功能比 synchronized 强:
lockInterruptibly() 在等待时可被中断。new ReentrantLock(true) 公平锁。tryLock(2, TimeUnit.SECONDS) 超时返回。Condition 灵活实现 wait/notify。和 synchronized 对比:
| 维度 | synchronized | ReentrantLock |
|---|---|---|
| 实现 | JVM 内置 | JDK 代码(AQS) |
| 锁升级 | 有(偏向/轻量/重量) | 无(直接 CAS) |
| 可中断 | 否 | 是 |
| 公平锁 | 否 | 可选 |
| 性能 | JDK 6 优化后接近 | 高并发稍优 |
选型:简单场景用 synchronized,复杂场景用 ReentrantLock。
和 synchronized 对比是必问题。表格化对比最清晰。
tryLock 也一样。ReentrantReadWriteLock,读读共享、读写互斥、写写互斥,适合读多写少。ThreadLocal 给每个线程独享的变量副本,线程隔离。底层结构:
ThreadLocalMap(类比 HashMap)。典型用途:
@Transactional 用 ThreadLocal 传 Connection。内存泄漏原因:
WeakReference),value 是强引用。解决:用完手动 remove,try { ... } finally { threadLocal.remove(); }。
ThreadLocal 内存泄漏是经典面试题,必答"弱引用 key + 强引用 value + 线程池不释放"。
ThreadPoolExecutor 7 个核心参数:
执行流程:
线程池是高级 Java 必问。必答 7 个参数 + 执行流程。
newFixedThreadPool 用 LinkedBlockingQueue 无界,会 OOM;newCachedThreadPool 线程数无上限,会 OOM。生产必须手写 ThreadPoolExecutor。Future(JDK 5):异步任务结果,但只能阻塞 get(),不能链式、不能回调、不能组合。
CompletableFuture(JDK 8):Future 的超集,支持链式编排 + 回调 + 组合。
CompletableFuture 核心能力:
thenApply() 转换结果,thenAccept() 消费,thenRun() 不关心结果。thenCombine() 两个任务都完成后合并,allOf() 全部完成,anyOf() 任一完成。exceptionally() 兜底,handle() 同时处理结果和异常。supplyAsync() 用 ForkJoinPool.commonPool() 异步跑。实战:并行查询三个服务再合并结果,从串行 300ms 降到 100ms(最慢的那个)。
CompletableFuture 是现代 Java 必备。必答"链式 + 组合 + 异常处理"。
supplyAsync(supplier, customExecutor),别用默认的 ForkJoinPool(会被其他任务影响)。都是 AQS 实现的线程协作工具,但定位不同:
await(),工作线程每完成一个 countDown(),count=0 时主线程被唤醒。await(),所有线程到齐后一起放行。对比:
场景:
这两个工具是面试常考"辨析题"。必答 1等N vs N等N、一次性 vs 循环。
JDK 8 的 ConcurrentHashMap:
put 流程:
ConcurrentHashMap 是 Java 并发集合的"代表作"。必答 CAS + 锁头节点(不是 Segment)。
你简历里大量用 Spring Cloud、Spring Boot、APISIX,这些必问。
Spring Bean 生命周期(4 个阶段 + 9 个回调):
InstantiationAwareBeanPostProcessor)。@Autowired)。BeanPostProcessor.postProcessBeforeInitialization。@PostConstructInitializingBean.afterPropertiesSet()BeanPostProcessor.postProcessAfterInitialization(AOP 代理在这里创建)。@PreDestroyDisposableBean.destroy()必答基础。记住 4 阶段:实例化 → 填充 → 初始化 → 销毁。
postProcessAfterInitialization 把目标对象包装成代理对象。Spring Boot 自动装配 = "约定优于配置",核心是 3 个注解:
AutoConfigurationImportSelector。META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3+)或老的 spring.factories,加载所有 XxxAutoConfiguration 类。@ConditionalOnClass、@ConditionalOnBean、@ConditionalOnMissingBean)。Spring Boot 必问题。必答"imports 文件 + @Conditional 条件装配"。
spring.factories(META-INF 下);3.x 后用 imports(性能更好,支持按需加载)。@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) 或 spring.autoconfigure.exclude 配置。XxxAutoConfiguration + META-INF/spring/...imports 列出类名。常见 7 种失效场景:
this.method() 不走代理,最常见。解决:注入自己(@Autowired private Service self;)或 AopContext。try { ... } catch (Exception e) { log(); },事务不会回滚。rollbackFor = Exception.class。Propagation.NOT_SUPPORTED、NEVER 等会挂起/禁止事务。这是"送命题"——99% 的 Java 程序员都踩过坑。必答"自调用"和"异常被 catch"。
this 是原对象,绕过代理,注解失效。logging.level.org.springframework.transaction.interceptor=TRACE。Spring AOP 是动态代理实现的,2 种方式:
Proxy.newProxyInstance()。基于接口。spring.aop.proxy-target-class=true),用 CGLIB 生成子类。基于继承,Spring Boot 2+ 默认。Spring 选择逻辑:
流程:
AOP 是 Spring 核心,必答 JDK vs CGLIB。
定义:A 依赖 B,B 依赖 A,构造时死循环。
Spring 通过三级缓存解决:
流程(A 依赖 B,B 依赖 A):
局限:构造器注入的循环依赖无法解决(实例化前就要依赖),需要用 @Lazy 或改成 setter 注入。
三级缓存是 Spring 高频题。必答"三级缓存 + 早期引用"。
@Lazy 延迟加载,打破循环。或者改用 setter / 字段注入。一个 HTTP 请求到响应,9 个核心组件:
经典题,必答 9 组件(或者简化版:DS → HM → HA → Controller → View → 响应)。
主流注册中心对比:Eureka / Nacos / Consul / Zookeeper。
核心三要素:
Nacos(推荐)特点:
Eureka 特点:
微服务基础。必答"注册 + 发现 + 心跳"。
Spring Cloud Gateway 是 Spring 官方推出的API 网关,基于 WebFlux + Netty(响应式,非阻塞)。
核心概念:
请求流程:
AddRequestHeader、StripPrefix、Hystrix(已废弃,用 Sentinel)。RequestRateLimiter + Redis 令牌桶。和 Zuul 对比:Gateway 性能更好(Netty 非阻塞),Zuul 1.x 是阻塞的,已淘汰。
必答核心三件套:Route + Predicate + Filter。
RequestRateLimiter 基于 Redis 计数。OpenFeign 是声明式 HTTP 客户端,像调用本地方法一样调用远程服务。
核心原理:
@FeignClient 注解的接口被 Spring 扫描到,创建代理对象。和 Ribbon 集成:客户端负载均衡,从注册中心拿服务列表,本地选一个实例。
和 Sentinel 集成:熔断降级。
OpenFeign 是微服务调用基础。必答"动态代理 + 注解解析"。
@RequestPart + MultipartFile,Feign 自带支持。feign.client.config.default.connectTimeout=5s,readTimeout=10s。Sentinel 是阿里开源的流量控制 + 熔断降级组件,比 Hystrix 功能强、UI 好用。
核心功能:
和 Hystrix 对比:Sentinel 功能更全、有 Dashboard、规则可动态配置,Hystrix 已停维。
必答"限流 + 熔断"两类核心功能。
push 模式推到 Nacos / Apollo。你简历上有一卡通项目对接微信/支付宝/银联,这是核心基础。但 JD 还要求 Apple Pay / Google Pay / Stripe / PayPal,面试前补一下国际化思路。
以 JSAPI / H5 支付为例,完整流程:
https://api.mch.weixin.qq.com/pay/unifiedorder,传 appid、mch_id、out_trade_no、total_fee、notify_url 等,用商户 key 做 MD5/HMAC-SHA256 签名。WeixinJSBridge.invoke;H5 走 https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb。这是支付类问题"基础题"。必答"统一下单 + 异步回调 + 验签"三件套。
natapp / cpolar 内网穿透。微信 / 支付宝回调是 "至少一次" 语义,重复回调 100% 发生,必须做幂等。三种幂等策略:
SET out_trade_no 1 NX EX 600,存在就跳过。快但要兜底。UPDATE order SET status='paid' WHERE out_trade_no=? AND status='unpaid',影响行数 = 0 就是重复。支付系统的"金标准":乐观锁 + 业务状态机,双保险。
这是支付"第一高频题"。必答"幂等"和"状态机"。
UPDATE order SET status='paid' WHERE out_trade_no=? AND status='unpaid',影响行数 0 就直接 return SUCCESS,2 年 0 重复扣款。"定义:同一个支付请求,无论执行多少次,结果都和执行一次一样。
四层幂等保障:
out_trade_no(商户订单号)。transaction_id。未支付 → 支付中 → 已支付 → 已退款。支付中 → 已支付 流转时处理,其他状态都跳过。UNIQUE KEY (out_trade_no)。WHERE status='unpaid'。out_trade_no 5s,防止并发请求同时处理。幂等是分布式系统的通用问题,支付场景尤其严格。必答"四层保障"。
tryLock 5s,超时放弃。补单:用户实际支付成功,但商户系统没收到回调(网络丢、回调超时、商户系统故障),需要主动查。
补单流程:
https://api.mch.weixin.qq.com/pay/orderquery 查订单真实状态。SUCCESS + 本地是 未支付 → 走正常支付成功流程(幂等处理)。关键点:
补单是"生产必备"。100% 的支付系统都有补单 + 对账机制。
@Scheduled(单机) / XXL-Job / Elastic-Job(分布式,避免重复)。本地消息表是经典的最终一致性方案,由 eBay 提出。
核心思路:
local_message 表,原子操作。local_message 表,把"未发送"的消息发到 MQ。UPDATE local_message SET sent=1。优点:实现简单、不依赖 2PC 协议。
缺点:有延迟(秒级到分钟级)、要维护消息表。
本地消息表是支付后置处理的"标准方案"。必答"业务 + 消息同库写"。
常见 4 种方案对比:
选型:支付后置业务(发短信、加积分) → 本地消息表;扣库存 → TCC;简单业务 → MQ 事务消息。
分布式事务是高级必问。必答 4 方案对比 + 选型。
支付回调处理 7 步:
out_trade_no 或 transaction_id 判断是否已处理。必答"验签 + 幂等 + 返回 SUCCESS"三件套。
海外支付几个核心差异:
JD 明确要求海外支付,必须答出和国内支付的差异。这是你的薄弱点。
JD 明确要求"熟练使用 GitHub Copilot、Kite 等 AI 编码辅助工具"。你有 Cursor 经验,结合实际使用讲。
我的 AI 工具栈:
典型使用场景:
JD 明确要求,回答要具体到工具 + 场景,体现你"真用过"而不是"听说过"。
Cursor 核心功能:
高效技巧:
JD 明确要求,回答要展现"你真在用"不是"听说过"。
.cursorrules 例子:"我在 Spring Boot 项目里写 Always use constructor injection, never field @Autowired,AI 自动遵守。"AI 提效的5 个层级:
我们项目的提效实践:
JD 明确要求。要给"具体数字"和"具体场景",不能只说"提高效率"。
MCP(Model Context Protocol):Anthropic 提出的模型上下文协议,让 LLM 调用外部工具 / 数据源 的标准协议。
类比:
架构:
类比 Function Calling:
MCP 是 2024-2025 年新概念,JD 没明确提但提到 Dify 暗示 AI Agent 能力。答出 MCP + Agent 是加分项。
@modelcontextprotocol/sdk(Node.js)或 Python SDK;定义工具 → 启动 Server → Host 接入。Dify:开源的LLM 应用开发平台,可视化搭建 AI 工作流,类似 Coze。
核心能力:
典型场景:
Dify 是国内 AI 工具的代表,JD 提了要了解。如果没做过,就说"了解过 + 学习过"。
Spring AI:Spring 官方的 AI 集成框架,2024 年发布,对标 LangChain4j。
核心能力:
PromptTemplate 管理 prompt,支持多轮对话。和 LangChain4j 对比:
Spring AI 是 Java 程序员做 AI 的"原生选择"。JD 提了,即使没做项目也要了解。
spring.ai.qwen.api-key + base-url,和 OpenAI 一样。@Tool 注解 Java 方法,LLM 自主决定调用。