📘 《Java面试85题图解版(二)》进阶深化上篇:并发编程 + JVM
阅读提示 :这是"图解+比喻+一句话总结"面试题库第二篇的进阶深化上篇 ,覆盖并发编程(7题)与JVM(7题)共14道面试高频题。每道题仍然是四层结构------结构图 → 场景比喻 → 关键对比表 → 一句话总结。
一、并发编程(第30-36题)
📌 第30题:四种创建线程的方式
一图看清
1. 继承Thread → 重写run()
2. 实现Runnable → 实现run()
3. 实现Callable → 实现call(),可返回结果,抛异常
4. 线程池(推荐) → 复用线程,控制资源
JDK21新增:虚拟线程 → 百万级并发,无需池化
比喻记忆 :请人干活
- 继承Thread:指定某人是干活的料,直接让他干。
- Runnable:写一张任务单,给谁都能执行。
- Callable:任务单+回执,干完还给你结果。
- 线程池:雇几个专职人员,来活了分配,干完回来待命。
- 虚拟线程:临时工零成本,需要时直接雇,用完就走。
💡 一句话总结 :简单用Runnable,需返回值用Callable,生产必须线程池,JDK21可上虚拟线程。
📌 第31题:synchronized 锁升级过程
一图看清
无锁 ─→ 偏向锁 ─→ 轻量级锁(自旋CAS) ─→ 重量级锁(OS Mutex)
同一线程多次进 少量线程交替执行 激烈竞争,挂起唤醒
比喻记忆 :会议室使用规则
- 无锁:会议室空着,谁都能用。
- 偏向锁:小王经常用,门上贴"小王专用",直接进。
- 轻量级锁:变成先到先得,小李偶尔来,门口徘徊等(自旋),抢到就进。
- 重量级锁:人太多了,保安介入------"都别在门口转了,下来排队,叫号再进"。挂起唤醒慢。
💡 一句话总结 :无锁→偏向→轻量级→重量级,竞争越激烈锁越重,只升不降。
📌 第32题:volatile 的可见性与有序性
一图看清
可见性 ✅:写后立即刷新主存,读前强制从主存取
有序性 ✅:禁止指令重排序(内存屏障)
原子性 ❌:i++ 复合操作仍不安全
比喻记忆 :大堂公告屏 vs 部门小黑板
- 普通变量:各部门自己的小黑板。老板在财务部写了"销售额1亿",销售部看不见。
- volatile:大堂LED公告屏,老板一写所有人都看见。但不能做"把总数+1"这种需要读+改+写三步的操作,三步中间别人也可能动。
💡 一句话总结 :volatile一写全公司可见,顺序不乱,但复合操作仍需加锁。
📌 第33题:synchronized vs ReentrantLock
一图看清
synchronized:JVM内置,自动加解锁,非公平,单条件队列
ReentrantLock:API级,手动加解锁(try/finally),可公平,多Condition,可尝试加锁(tryLock)
比喻记忆 :电子门禁 vs 机械钥匙
- synchronized:进门自动锁门,出门自动解锁。简单但功能少。
- ReentrantLock:手动钥匙,可以公平排队,可以有多个等待区(Condition),可以试试"现在能不能开"(tryLock)。
💡 一句话总结 :简单场景synchronized够用;需要Condition/可中断/尝试加锁时用ReentrantLock。
📌 第34题:线程池核心参数与工作流程
一图看清
新任务 →
├─ 核心线程未满 → 创建核心线程
├─ 核心线程满 → 入队列排队
│ ├─ 队列未满 → 等待执行
│ └─ 队列满 → 线程数<最大?→ 创建非核心线程
│ └─ 已达最大 → 拒绝策略
└─ 空闲超时(keepAliveTime) → 非核心线程销毁
不推荐Executors:无界队列可能OOM,最大线程数可能Integer.MAX_VALUE
比喻记忆 :银行柜台
- corePoolSize:5个常驻柜台,客户来了直接办。
- workQueue:10个等候座位,坐满了经理才着急。
- maximumPoolSize:临时开3个流动柜台,总共最多8个。
- 拒绝策略:大厅站不下了,保安拦人"明天再来"。
- keepAliveTime:高峰期过后,空闲临时柜台5分钟撤掉。
💡 一句话总结 :5常驻柜→10座等候→8临时极限→满了就拒,闲了撤临柜,绝不用Executors偷懒。
📌 第35题:ThreadLocal 原理与内存泄漏
一图看清
Thread → ThreadLocalMap → Entry(key弱引用ThreadLocal → value强引用)
当外部强引用ThreadLocal置null → key被GC → Entry的value仍强引用 → 泄漏
线程池中线程长期复用 → ThreadLocalMap一直存在 → value无法回收
比喻记忆 :公司衣柜
- Thread = 员工,ThreadLocalMap = 员工专属衣柜。
- key弱引用 = 抽屉标签,撕掉后没人知道里面装啥。
- value强引用 = 抽屉里的羽绒服,只要员工(线程)不走,羽绒服就占着柜子。
- 线程池员工是长期工,衣柜越塞越满,必须每次用完清空------
remove()。
💡 一句话总结 :ThreadLocal的弱引用key被GC后value赖着不走,线程池必须手动remove。
📌 第36题:CompletableFuture 异步任务编排
一图看清
supplyAsync → 启动异步任务,返回结果
thenApply → 转换结果
thenAccept → 消费结果,无返回
thenCombine → 合并两个独立任务的结果
thenCompose → 依赖上一步结果启动新任务(扁平化)
allOf / anyOf → 等所有完成 / 任一完成
exceptionally / handle → 异常兜底
比喻记忆 :餐厅取餐牌
- supplyAsync:下单"宫保鸡丁",拿到取餐牌。
- thenApply:菜好了自动加一份米饭(转换结果)。
- thenCombine:鸡丁和鱼香肉丝同时做,好了放同一个托盘。
- thenCompose:先看今日特价菜单,再决定点哪个(依赖第一步结果)。
- allOf:宴请,所有菜齐了才开吃。
- exceptionally:哪道菜做砸了,赔个水果盘。
💡 一句话总结 :CompletableFuture像取餐牌,能串行、并行、合并、兜底,告别Future.get()阻塞。
二、JVM(第37-43题)
📌 第37题:JVM 运行时数据区
一图看清
线程共享:
堆(对象实例、数组) | 方法区/元空间(类元信息、运行时常量池)
线程私有:
程序计数器 | 虚拟机栈(栈帧:局部变量表、操作数栈) | 本地方法栈
比喻记忆 :公司办公大楼
- 堆:公共办公区,所有员工共用。保洁阿姨(GC)清理没人用的工位。
- 方法区/元空间:公司档案室,保存组织架构、规章制度。JDK8后档案室搬到地下室(本地内存),不占主楼面积。
- 虚拟机栈:员工个人工作记录本,任务完成翻回上页(方法返回)。
- 程序计数器:手指指着当前正在执行哪一行代码。
💡 一句话总结 :堆和方法区大家共享,栈、PC寄存器、本地栈各线程独有。
📌 第38题:Java 堆的分代模型
一图看清
新生代(Eden + S0 + S1,默认8:1:1)
↓ Minor GC存活后年龄+1 → 进入Survivor区
↓ 年龄达到阈值(默认15)→ 晋升老年代
老年代
↓ 大对象可直接进入
JDK8+:永久代→元空间(本地内存)
JDK9+:G1默认收集器,逻辑分代
JDK21:分代ZGC
比喻记忆 :公司员工分级
- 新生代(Eden):试用期员工,大部分待不久就被优化。
- S0/S1:转正考核区,每熬过一次考核年龄+1。
- 老年代:老员工,熬过15次考核(或大对象直接空降高管)。
- 元空间:公司规章制度,不占工位。
💡 一句话总结 :新生代复制回收,熬过去进老年代,元空间存类信息不占堆。
📌 第39题:垃圾回收算法
一图看清
标记-清除:标记存活 → 清除未标记 → 产生内存碎片
复制算法:内存分两块 → 存活复制到另一块 → 清空当前块 → 浪费一半内存
标记-整理:标记存活 → 向一端移动 → 清边界外 → 无碎片但STW时间长
分代收集:新生代用复制,老年代用标记-清除/整理
比喻记忆 :三种搬家方式
- 标记-清除:贴"要/不要"标签,不要的扔出窗外。房间空了但家具乱七八糟,大沙发放不下(内存碎片)。
- 复制算法:你有A、B两间房,把A里还要的搬到B,一把火烧掉A。整齐但永远只用一半房间。
- 标记-整理:把还要的推到房间一端,尾部全清空。最整齐但搬家最累(移动对象开销大)。
💡 一句话总结 :新生代复制(快),老年代整理(无碎片),分代组合是主流。
📌 第40-41题:常见垃圾回收器及CMS vs G1
一图看清
Serial / Serial Old → 单线程,小内存Client模式
ParNew + CMS → 低延迟,并发标记清除,碎片+浮动垃圾,JDK8常用
Parallel Scavenge + Parallel Old → 关注吞吐量,后台计算
G1 → Region分区+可预测停顿,JDK9+默认
ZGC / Shenandoah → 超低延迟(<1ms),JDK21分代ZGC
比喻记忆 :清洁工团队
- Serial:一个人干全部,小房间够用。
- CMS:几个人边干活边让人用房间,低延迟但房间不整齐(碎片),偶尔要大扫除(退化为Serial Old全STW)。
- G1:把大楼划分成多个区域,哪块最脏先清哪块,在规定时间内完成。JDK9后默认清洁团队。
- ZGC:几乎看不见的清洁工,你感觉不到他们在工作。
💡 一句话总结 :低延迟CMS(老)→G1(JDK9+默认)→ZGC(JDK21分代,极低延迟)。
📌 第42题:类加载过程与双亲委派模型
一图看清
类加载过程:加载 → 验证 → 准备 → 解析 → 初始化
读取 格式 分配 符号 static
字节码 检查 赋零值 引用→ 赋值
直接引用
双亲委派:请求向上委派(子→父→爷),加载向下查找(爷→父→子)
Bootstrap ClassLoader → Extension ClassLoader → Application ClassLoader → 自定义
比喻记忆 :新员工入职 + 小孩要零花钱
- 加载:HR拿到简历,录入系统。
- 验证:背调,检查学历证真伪、有无犯罪记录。
- 准备:分配工位、电脑,但桌上还是空的(static变量赋零值)。
- 解析:把"隔壁部门张三"这种称呼转成工号(符号引用→直接引用)。
- 初始化:新员工到岗,摆个人物品,装软件(执行static赋值和static代码块)。
双亲委派:小孩要零花钱,先问爸,爸问爷。爷有就给,都没有爸爸才自己掏。防止小孩用假钞(自定义String)冒充真钱。
💡 一句话总结 :加载五步走,双亲委派向上请求向下加载,护住核心库不被篡改。
📌 第43题:OOM 排查与定位
一图看清
常见OOM类型:
Java heap space → 堆溢出,对象太多
Metaspace → 元空间不够,类加载过多
unable to create new native thread → 线程数超系统限制
排查工具链:
jstat → 监控GC和内存
jmap -dump → 导出堆快照
jstack → 看线程状态
MAT / JProfiler → 分析dump文件
GC日志 → -XX:+PrintGCDetails
比喻记忆 :查漏水
- jstat + 监控:水管压力表,发现水位在涨。
- jmap dump:拆开天花板,看哪段管子出问题。
- MAT分析:工程师分析是哪个厂家的管道老化了。
- 解决:换管(修代码)、调水压(调JVM参数)、加装蓄水池(扩容内存)。
💡 一句话总结 :监控+堆dump+MAT定位根源,修代码或调参,ThreadLocal和集合未清理是最常见泄漏点。
📌 下篇预告
本篇(上篇)我们覆盖了并发编程从锁升级到CompletableFuture 、JVM从运行时数据区到OOM排查的全部核心知识。这些正是面试官在中后段最爱追问的深水区。
下一篇(进阶深化中篇) 将进入 Spring核心与Spring Boot + 数据库进阶,涵盖IoC、AOP、循环依赖、自动装配、事务失效,以及MySQL与PostgreSQL的日志体系、索引类型、复制机制、分库分表等17道题。
👉 点击关注我,进阶深化中篇更新后第一时间推送!