Skip to content

面试题蒸馏卡: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)——

  1. 用 MAT 等工具分析 dump 快照,确认对象是否必要
  2. 若是泄漏:看泄漏对象到 GC Roots 的引用链,定位泄漏代码
  3. 若不是泄漏:检查 -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 时会

常见误解:❌ 用无界队列 + 无界线程;❌ 以为线程池创建即建线程(懒加载)。

关联

最近更新