主题
面试题蒸馏卡:JVM 与并发
从
语雀-Java开发/JVM+多线程与高并发37 篇素材提炼的高频面试题。每题按「标准答 → 追问链 → 常见误解」组织。详细机制见对应概念卡。
Q1:说说 JVM 的内存结构(运行时数据区)
标准答:JVM 把内存划成线程私有的程序计数器、虚拟机栈、本地方法栈 + 线程共享的堆、方法区,外加堆外的直接内存。
- 程序计数器:记录字节码指令地址,唯一不 OOM 的区域
- 虚拟机栈:存栈帧(局部变量表/操作数栈/动态链接/返回地址),方法调用即压栈
- 堆:对象实例 + 数组,GC 主战场,分新生代(Eden:S0:S1=8:1:1)+ 老年代
- 方法区:类型信息/常量/静态变量/JIT 代码,JDK8 后由元空间(本地内存)实现
追问链:
- "栈和堆的区别?" → 栈管执行(线程私有、存引用)、堆管存储(线程共享、存对象)
- "方法区和永久代是一回事吗?" → 永久代是 HotSpot 对方法区的一种实现,JDK8 换成元空间
- "字符串常量池在哪?" → JDK7 起移到堆(原来在永久代,GC 效率低)
- "什么情况会 StackOverflowError / OOM?" → 栈深度超限 / 堆或元空间不足
常见误解:❌ 漏了方法区/程序计数器,只答"堆和栈";❌ 以为栈里存对象(栈存的是引用)。
Q2:类加载过程和双亲委派是什么?
标准答:类加载 7 阶段——加载→验证→准备→解析→初始化→使用→卸载(验证+准备+解析=连接)。双亲委派:类加载请求先交给父加载器,父加载不了才自己加载,加载顺序自底向上、动作自顶向下。
追问链:
- "为什么双亲委派?" → 避免重复加载 + 防止核心 API 被篡改(自定义
java.lang.String轮不到自己加载) - "怎么打破?" → 重写
loadClass()(Tomcat 应用隔离、JDBC SPI) - "准备阶段和初始化阶段赋值区别?" →
static int a=1准备阶段是 0、初始化才赋 1;static final int C=10(编译期常量)准备阶段就赋好 - "
<clinit>和<init>区别?" → 前者管 static(类初始化),后者管实例(对象构造)
常见误解:❌ 以为 ClassLoader.loadClass() 会初始化(它只加载不初始化,Class.forName() 才初始化)。
Q3:JVM 怎么排查 OOM / 内存溢出?
标准答:先区分内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)——
- 用 MAT 等工具分析 dump 快照,确认对象是否必要
- 若是泄漏:看泄漏对象到 GC Roots 的引用链,定位泄漏代码
- 若不是泄漏:检查
-Xmx/-Xms能否调大,或找生命周期过长、持状态过久的对象
追问链:
- "Minor/Major/Full GC 区别?" → 新生代/老年代/整堆+方法区;Full GC 尽量规避
- "Full GC 触发条件?" → System.gc()、老年代不足、方法区不足、晋升均值超老年代、复制担保失败
- "怎么快速定位频繁 Full GC?" → jstat 看 GC 频率、jmap dump、Arthas 看对象分布
常见误解:❌ 不区分泄漏和溢出就盲目调大 -Xmx(泄漏场景调大只会延缓崩溃)。
Q4:synchronized 和 ReentrantLock 的区别?
标准答:
- 相同点:都是悲观锁、可重入锁
- synchronized:JVM 关键字,锁升级(无锁→偏向→轻量级→重量级),自动加解锁,不可中断、不可超时、非公平
- ReentrantLock:API 层,基于 AQS,可中断(lockInterruptibly)、可超时(tryLock)、可公平、可多条件(Condition)
追问链:
- "synchronized 锁升级过程?" → 偏向锁(同线程反复加锁,1 次 CAS)→ 轻量级锁(CAS+自旋)→ 重量级锁(OS Mutex,阻塞)
- "ReentrantLock 怎么实现可重入?" → AQS 的 state 计数,lock 一次 +1、unlock 一次 -1
- "公平锁 vs 非公平锁?" → 公平按队列顺序(不饥饿、吞吐低),非公平可插队(吞吐高、可能饥饿)
常见误解:❌ 以为两者性能天差地别(JDK6 优化后接近,差异在功能不在速度);❌ 以为锁状态能降级(只能升级)。
Q5:volatile 的作用和原理?
标准答:volatile 保证可见性(写立即刷主存、读强制从主存取)和有序性(禁止指令重排),但不保证原子性。原理是 JMM 的 Happens-Before 规则:volatile 写 HB 后续读。
追问链:
- "volatile 能保证 count++ 原子吗?" → 不能,count++ 是读-改-写复合操作,要 CAS 或加锁
- "DCL 单例为什么要 volatile?" → 防止重排导致拿到半初始化对象(new 的三步:分配内存→初始化→赋引用,重排后引用先暴露)
- "volatile 和 CAS 分工?" → volatile 管可见性、CAS 管原子更新
常见误解:❌ 把 volatile 当锁用(它不保证原子性);❌ 以为 volatile 变量读写很慢(现代 JVM 开销已很小)。
Q6:线程池参数和执行流程?
标准答:ThreadPoolExecutor 核心参数 corePoolSize / maximumPoolSize / keepAliveTime / workQueue / threadFactory / handler。执行流程三步:线程数 < core → 建线程;否则入队;队列满且 < max → 再建线程,否则拒绝。
追问链:
- "线程数怎么定?" → CPU 密集 N+1、I/O 密集 2N
- "为什么禁用 Executors.newFixedThreadPool?" → 无界队列(LinkedBlockingQueue 默认 Integer.MAX_VALUE),任务堆积 OOM
- "submit 和 execute 异常处理区别?" → submit 异常封装进 Future,get() 时抛;execute 异常需任务内 try-catch 或 afterExecute/UncaughtExceptionHandler
- "核心线程会销毁吗?" → 默认不会(take 阻塞保活),
allowsCoreThreadTimeOut=true时会
常见误解:❌ 用无界队列 + 无界线程;❌ 以为线程池创建即建线程(懒加载)。
关联
- 总览:JVM技术栈总览 · 并发编程技术栈总览
- 域地图:A00-百科/Java后端/Java后端