跳到主要内容 Java 高级工程师高频核心面试题与解析 | 极客日志
Java java 算法
Java 高级工程师高频核心面试题与解析 整理 Java 高级工程师面试高频核心问题,涵盖 JVM 内存结构、GC 机制、并发编程、集合源码、Spring 全家桶、MySQL 优化及分布式理论。提供标准答案与深度解析,帮助开发者掌握技术细节,提升面试通过率。
DotNetGuy 发布于 2026/3/30 更新于 2026/4/13 0 浏览一、JVM 虚拟机(重中之重,必问,分值最高)
1. JVM 内存结构(运行时数据区),JDK8 做了什么重大改动?
答:JVM 运行时数据区包含 方法区、堆、虚拟机栈、本地方法栈、程序计数器 5 个区域,线程私有 :虚拟机栈、本地方法栈、程序计数器;线程共享 :堆、方法区。
程序计数器:记录当前线程执行的字节码行号,唯一不会 OOM 的区域;
虚拟机栈:每个方法执行创建栈帧,存储局部变量表、操作数栈等,方法调用入栈、执行完出栈,溢出抛出 StackOverflowError;
堆:JVM 最大内存区域,唯一的 GC 核心区域 ,存储所有对象实例和数组,溢出抛出 OutOfMemoryError: Java heap space;
本地方法栈:为 native 本地方法服务,和虚拟机栈原理一致,同样会栈溢出 / OOM;
方法区:存储类信息、常量、静态变量、即时编译后的代码,溢出抛出 OutOfMemoryError: Metaspace/ 永久代 OOM。 JDK8 核心改动(必考) :① 移除永久代(PermGen),改用 元空间(Metaspace) 替代,元空间不在 JVM 堆内存中,而是使用 本地内存(直接内存) ;② 字符串常量池、静态变量从永久代迁移到 堆内存 中;③ 原因:永久代内存大小固定,容易 OOM;元空间内存受本机物理内存限制,内存溢出概率大幅降低。
2. 垃圾回收(GC)核心三连问:哪些内存需要回收?什么时候回收?怎么回收?
✅ 哪些内存需要回收?
堆内存 + 方法区(元空间)的内存需要回收;虚拟机栈、本地方法栈、程序计数器随线程销毁自动释放,无需 GC 回收。
核心:回收堆中不再被引用的对象实例 + 方法区中无用的常量、无用的类 。
✅ 什么时候回收?(触发 GC 的时机)
1)Minor GC(新生代 GC)触发时机 新生代(Eden 区 + Survivor 区)的 Eden 区满了就触发 ,频率极高、速度极快,回收效率高,停顿时间短。
2)Major GC/Full GC(老年代 GC / 整堆 GC)触发时机(高频考点,背熟) ① 老年代空间不足时触发;② Minor GC 后,存活对象无法放入 Survivor 区,需要晋升到老年代,但老年代没有足够空间,会 提前触发 Full GC ;③ 方法区(元空间)内存不足时触发;④ 调用 System.gc() 时,JVM 大概率触发 Full GC (只是建议,不是强制);⑤ 堆中分配大对象时,新生代放不下、老年代也放不下,触发 Full GC。
✅ 怎么回收?(垃圾回收算法 + 垃圾收集器)
✔ 4 种核心垃圾回收算法
标记 - 清除:标记存活对象,清除未标记对象;缺点:产生内存碎片、效率低 (标记 + 清除两步都慢);
复制算法:把内存分为两块,只用一块,满了就把存活对象复制到另一块,清空原内存;新生代核心算法 (Eden:S0:S1 = 8:1:1);优点:无内存碎片、效率高;缺点:内存利用率低;
标记 - 整理:标记存活对象,把存活对象向内存一端移动,然后清除边界外的内存;老年代核心算法 ;优点:无内存碎片、内存利用率 100%;缺点:效率低(多了移动步骤);
分代收集:JVM 默认的组合算法,新生代用复制算法,老年代用标记 - 整理 / 标记 - 清除 ,结合了所有算法的优点,是最优解。
✔ 垃圾收集器(JDK8 默认 + JDK9 以后默认,必考)
JDK8 默认组合:Parallel GC(新生代) + Parallel Old GC(老年代) ,并行回收,吞吐量优先,适合后台计算、大数据分析等场景;
JDK9 及以后默认组合:G1 GC(Garbage First) ,分区回收,兼顾吞吐量和停顿时间,是目前主流的收集器,适合大内存、高并发场景;
高并发核心收集器:CMS GC (老年代)+ ParNew(新生代),并发回收,停顿时间优先,适合电商、金融等对响应时间要求高的场景;缺点:内存碎片多、CPU 占用高;
ZGC/Shenandoah GC:JDK11 + 的低停顿收集器,停顿时间毫秒级,适合超大内存(百 G 级)场景。
3. 如何判断一个对象是否存活?(两种核心算法) 答:JVM 判断对象存活的核心是「该对象是否还有引用可达」,两种算法:
引用计数法 :给每个对象加引用计数器,被引用 + 1,引用失效 - 1,计数器 = 0 则标记为垃圾;缺点 :无法解决 循环引用 问题(比如 A 引用 B,B 引用 A,两者计数器都不为 0,但实际无任何外部引用),JVM 不使用 该算法;
可达性分析算法 :JVM 唯一使用 的算法,以「GC Roots」为根节点,向下遍历引用链,如果对象到 GC Roots 没有任何引用链相连,说明对象不可达 ,标记为垃圾;✔ 可作为 GC Roots 的对象:虚拟机栈中引用的对象、本地方法栈中 native 方法引用的对象、方法区中静态变量 / 常量引用的对象。
4. Java 中的四种引用类型(必考,结合 GC) 答:从强到弱依次是:强引用 > 软引用 > 弱引用 > 虚引用 ,核心区别是:被 GC 回收的时机不同 ,JDK 提供这四种引用是为了 灵活控制对象的生命周期 。
强引用(Strong Reference):最常用的引用,比如 Object obj = new Object();只要强引用存在,GC 永远不会回收该对象,内存不足时直接抛出 OOM;
软引用(Soft Reference):用 SoftReference 包裹,内存足够时不回收,内存不足时(即将 OOM 时)一定会回收 ;适合做缓存(比如图片缓存);
弱引用(Weak Reference):用 WeakReference 包裹,只要触发 GC,无论内存是否充足,都会回收 ;典型应用:WeakHashMap,缓存场景;
虚引用(Phantom Reference):最弱的引用,用 PhantomReference 包裹,无法通过虚引用获取对象实例 ,唯一作用是:对象被 GC 回收时,会收到一个系统通知;典型应用:管理堆外内存。
5. JVM 调优核心问题(大厂必问,加分项)
✅ 调优目标 ① 减少 Full GC 的频率(Full GC 耗时极长,会导致程序卡顿);② 控制 Minor GC 的停顿时间(Minor GC 频率高但耗时短,可接受);③ 核心指标:吞吐量优先 或 响应时间优先 。
✅ 常用核心 JVM 参数(必背,高频) # 堆内存核心参数(必须设置,最常用)
-Xms1024m # 初始堆内存大小,建议和-Xmx 一致,避免内存扩容带来的性能损耗
-Xmx1024m # 最大堆内存大小
-Xmn512m # 新生代内存大小,建议占堆内存的 1 /2 ~ 1 /3
-XX:SurvivorRatio=8 # Eden:S0:S1 = 8 :1 :1 (默认值)
# 垃圾收集器指定
-XX:+UseParallelGC
-XX:+UseParallelOldGC # JDK8 默认,并行回收,吞吐量优先
-XX:+UseG1GC # 指定 G1 收集器,主流首选
# 溢出打印(线上环境必加,排查 OOM 核心)
-XX:+HeapDumpOnOutOfMemoryError # OOM 时自动生成堆转储文件
-XX:HeapDumpPath=./dump.hprof # 堆转储文件存储路径
✅ 调优步骤(标准答案)
线上环境添加上述核心参数,OOM 时生成堆转储文件;
用工具(MAT、JProfiler、VisualVM)分析堆转储文件,定位内存泄漏 / 内存溢出的对象;
调整堆内存大小、新生代比例,选择合适的垃圾收集器;
优化代码:解决内存泄漏(比如关闭未释放的连接、清空无用的集合、避免静态集合持有大量对象);
压测验证,逐步调优,直到达到目标。
二、Java 并发编程(核心必考,占比 30%+,深挖到底)
1. 进程和线程的区别?为什么要用多线程?
进程 :操作系统资源分配的最小单位,有独立的内存空间,进程之间相互隔离,切换开销大;
线程 :CPU 调度和执行的最小单位,是进程的子集,共享进程的内存空间(堆、方法区),线程私有(虚拟机栈、本地方法栈、程序计数器),切换开销极小;
为什么用多线程?三大核心好处 :① 提升 CPU 利用率:多核 CPU 下,多线程可以并行执行,避免 CPU 空闲;② 提升程序响应速度:比如电商系统,主线程处理用户请求,子线程处理订单、支付等耗时操作,用户无需等待;③ 简化程序设计:复杂业务拆分为多个线程执行,代码更清晰,逻辑更简单。
2. 线程的生命周期(6 种状态,必考,标准答案) 答:Java 线程的生命周期在 Thread.State 枚举中定义,共 6 种状态 ,状态流转是面试重点:NEW(新建) → RUNNABLE(就绪/运行) → BLOCKED(阻塞) → WAITING(等待) → TIMED_WAITING(超时等待) → TERMINATED(终止)
NEW:线程对象创建完成,但未调用 start() 方法,此时线程未启动;
RUNNABLE:调用 start() 后进入该状态,包含「就绪」和「运行」两个子状态;就绪:线程等待 CPU 调度;运行:CPU 正在执行线程;
BLOCKED:线程因 竞争 synchronized 锁失败 而阻塞,释放锁后可重新进入 RUNNABLE;
WAITING:线程调用 wait()、join()、LockSupport.park() 后进入,无超时时间 ,必须被其他线程唤醒(notify()、notifyAll())才能退出;
TIMED_WAITING:线程调用 wait(long)、sleep(long)、join(long)、LockSupport.parkNanos() 后进入,有超时时间 ,超时自动唤醒;
TERMINATED:线程执行完毕 / 异常终止,生命周期结束。
3. synchronized 和 Lock 的区别?(顶级高频,必背) 答:两者都是 Java 的 线程同步工具 ,用于解决多线程并发安全问题,核心区别如下(标准答案,分点清晰):
底层实现 :
synchronized:Java 关键字 ,底层由 JVM 实现(C++),是 重量级锁 (JDK6 后做了锁升级优化:无锁→偏向锁→轻量级锁→重量级锁);
Lock:Java 接口 (核心实现 ReentrantLock),底层由 Java 代码实现,基于 AQS(抽象队列同步器),是轻量级锁;
锁的释放 :
synchronized:自动释放锁 ,线程执行完代码块 / 抛出异常时,锁会自动释放,无需手动处理,不会死锁;
Lock:必须手动释放锁 ,必须在 finally 块中调用 unlock(),否则锁永远不会释放,会导致死锁;
锁的获取 :
synchronized:线程获取锁失败时,会 一直阻塞 ,直到获取锁为止,无法中断、无法超时;
Lock:支持 非阻塞获取锁 (tryLock())、超时获取锁 (tryLock(long,TimeUnit))、可中断获取锁 (lockInterruptibly()),灵活性极高;
锁的类型 :
synchronized:可重入锁、非公平锁 (默认),无法手动设置公平 / 非公平;
Lock:ReentrantLock 是 可重入锁 ,支持手动设置公平锁 / 非公平锁(构造方法传 true 为公平锁);
功能扩展 :
synchronized:功能单一,仅支持简单的同步;
Lock:支持条件变量(Condition)、多条件唤醒、分段锁等,功能更强大;
使用场景 :简单同步用 synchronized(代码简洁、无死锁风险);复杂同步用 Lock(比如需要超时、中断、公平锁)。
4. 什么是死锁?产生死锁的 4 个条件?如何避免死锁?(必考)
✅ 死锁定义 多个线程同时持有对方需要的锁,且都不释放自己的锁,导致所有线程都无法继续执行,永久阻塞 的状态。
✅ 产生死锁的 4 个必要条件(缺一不可)
互斥条件:资源只能被一个线程持有,不能共享;
持有并等待:线程持有一个锁,同时等待另一个锁,且不释放已持有的锁;
不可剥夺条件:线程持有的锁,只能自己主动释放,不能被其他线程强行剥夺;
循环等待条件:多个线程形成环形的锁等待链(比如 A 等 B 的锁,B 等 C 的锁,C 等 A 的锁)。
✅ 如何避免死锁?(标准答案,破坏任意一个条件即可)
破坏循环等待条件 :统一锁的获取顺序 (最常用、最有效),比如所有线程都按「锁 A→锁 B→锁 C」的顺序获取锁;
破坏持有并等待条件 :一次性获取所有需要的锁,获取不到则释放已持有的锁;
破坏不可剥夺条件 :使用 Lock 的 tryLock() 超时获取锁,超时则释放已持有的锁;
尽量减少锁的持有时间,尽量使用无锁编程(比如原子类)。
5. volatile 关键字的作用?为什么能保证可见性、有序性,不能保证原子性?(顶级高频) 答:volatile 是 Java 的 轻量级同步关键字 ,是 线程安全的轻量级实现 ,性能远高于 synchronized,核心作用:保证变量的可见性、禁止指令重排序(有序性),但不能保证原子性 。
✅ 三大特性详解
保证可见性 :多线程环境下,一个线程修改了 volatile 变量的值,修改后的值会立即刷新到主内存 ,其他线程读取时,会 立即从主内存读取最新值 ,而不是读取自己的工作内存缓存,解决了「缓存不一致」问题。(补充:普通变量的修改,线程先改工作内存,再随机刷入主内存,其他线程可能读到旧值)
禁止指令重排序(有序性) :JVM 在编译和运行时,会对无依赖的代码做「指令重排序」优化,提升执行效率,但在多线程下会导致逻辑错误;volatile 变量会添加 内存屏障 ,禁止其前后的代码被重排序,保证代码的执行顺序和编写顺序一致。✔ 经典场景:单例模式的双重检查锁(DCL),必须给 instance 变量加 volatile,否则可能出现半初始化对象。
不能保证原子性 :原子性是指「一个操作要么全部执行成功,要么全部执行失败,中间不会被打断」;volatile 的底层是内存屏障,没有加锁,对于 复合操作 (比如 count++),本质是 读取→修改→写入 三步,这三步之间可能被其他线程打断,导致数据不一致;✔ 解决:原子性问题用 java.util.concurrent.atomic 包下的 原子类 (AtomicInteger、AtomicLong),底层是 CAS 实现,无锁且保证原子性。
6. CAS 是什么?ABA 问题是什么?如何解决?(必考)
✅ CAS 定义 CAS(Compare And Swap,比较并交换)是 无锁编程的核心思想 ,是一种乐观锁,底层由 CPU 的原子指令实现,保证操作的原子性;CAS 有 3 个核心参数:V(内存值)、A(预期值)、B(新值),逻辑:如果内存值 V 等于预期值 A,则将内存值更新为 B,否则不做任何操作 ,整个过程是原子的。✔ 应用:Java 原子类(AtomicInteger)、AQS、ConcurrentHashMap 等并发工具的底层都是 CAS。
✅ CAS 优点 无锁,不需要加锁 / 释放锁,没有线程阻塞的开销,并发性能远高于加锁 。
✅ CAS 缺点
ABA 问题 :CAS 的核心漏洞,下面重点说;
自旋开销 :如果 CAS 失败,线程会一直自旋重试,CPU 占用率高;
只能保证单个变量的原子性 :无法解决多个变量的复合操作原子性问题。
✅ ABA 问题 & 解决方案
① ABA 问题定义 线程 1 准备用 CAS 把变量值从 A 改成 B,在执行 CAS 之前,线程 2 把变量值从 A 改成 C,又改回 A;线程 1 执行 CAS 时,发现内存值还是 A,认为变量未被修改,执行更新操作,实际变量已经被修改过 ,这就是 ABA 问题。
② 解决方案 给变量 添加版本号 / 时间戳 ,每次修改变量时,版本号 + 1;CAS 时不仅比较变量值,还要比较版本号,只有「值相等 + 版本号相等」才执行更新;✔ Java 中的实现:AtomicStampedReference(带版本号的原子引用)、AtomicMarkableReference(带标记的原子引用)。
7. ThreadLocal 是什么?底层原理?内存泄漏问题?(必考) 答:ThreadLocal 是 Java 的 线程本地变量 ,核心作用:为每个线程创建一个独立的变量副本,线程之间互不干扰 ,解决了「多线程共享变量的并发安全问题」,是一种「空间换时间」的方案。
✅ 底层原理
每个 Thread 线程对象中,都持有一个 ThreadLocalMap 类型的成员变量 threadLocals;
ThreadLocalMap 是一个自定义的哈希表,key 是 ThreadLocal 对象本身(弱引用),value 是线程的变量副本 ;
当线程调用 ThreadLocal.set() 时,把变量副本存入当前线程的 ThreadLocalMap 中;调用 get() 时,从当前线程的 ThreadLocalMap 中读取;调用 remove() 时,删除当前线程的变量副本。
✅ 内存泄漏问题(高频深挖)
① 为什么会内存泄漏? ThreadLocalMap 的 key 是 弱引用 的 ThreadLocal 对象,value 是 强引用 的变量副本;当 ThreadLocal 对象被外部强引用释放后,GC 会回收这个 ThreadLocal 对象(因为 key 是弱引用),此时 ThreadLocalMap 中会出现「key=null,value≠null」的无效条目;如果线程一直存活(比如线程池的核心线程),value 的强引用永远不会被释放,导致 堆内存泄漏 。
② 如何避免内存泄漏?(标准答案) 使用完 ThreadLocal 后,必须手动调用 remove() 方法删除变量副本 ,这是唯一的解决方案;(补充:JDK 在 get()/set() 时会清理 key=null 的无效条目,但这是兜底策略,不能依赖)。
三、Java 集合源码(高频必考,HashMap 是重中之重)
1. HashMap JDK7 vs JDK8 核心区别(顶级高频,背熟,分点答) 答:HashMap 是 Java 最常用的集合,JDK8 对其做了 革命性优化 ,核心区别如下,全部是考点:
底层结构 :JDK7 是 数组 + 链表 ;JDK8 是 数组 + 链表 + 红黑树 ;
链表插入方式 :JDK7 是 头插法 ,扩容时会导致链表反转,多线程下可能出现死循环;JDK8 是 尾插法 ,扩容时链表顺序不变,解决了死循环问题;
红黑树转换条件 :JDK8 中,当链表长度 ≥8 ,且数组长度 ≥64 时,链表转为红黑树;链表长度≤6 时,红黑树转回链表;目的是平衡查询效率(红黑树查询时间复杂度 O(log n),链表 O(n));
哈希算法 :JDK8 的哈希算法更简洁,减少哈希冲突,计算效率更高;
扩容机制 :JDK8 扩容时,元素的新索引要么是原索引,要么是原索引 + 原数组长度,计算更简单;
初始化方式 :JDK7 是显式初始化(调用构造方法就创建数组);JDK8 是 懒加载 (第一次 put 时才创建数组),节省内存;
2. HashMap 的扩容机制?为什么容量是 2 的幂次?(必考)
✅ 扩容机制 HashMap 的默认初始容量是 16,负载因子是 0.75,阈值 = 容量 × 负载因子;当 HashMap 的元素个数 ≥阈值 或 链表转红黑树时数组长度不足,触发扩容,每次扩容都是原容量的 2 倍 ;扩容的核心是:创建新的数组,把原数组的元素重新哈希到新数组中,JDK8 优化了哈希计算,效率大幅提升。
✅ 容量为什么是 2 的幂次?(两大核心原因,标准答案)
哈希计算更高效 :计算元素在数组中的索引时,公式是 hash & (length-1);当 length 是 2 的幂次时,length-1 的二进制是全 1,哈希值与全 1 的数做与运算,等价于取模运算,效率远高于 % 取模 ;
减少哈希冲突 :2 的幂次的 length-1 二进制全 1,能让哈希值的所有位都参与运算,哈希结果更均匀,哈希冲突更少。
3. ConcurrentHashMap 线程安全的实现方式(JDK7 vs JDK8,必考) 答:ConcurrentHashMap 是 HashMap 的线程安全版本,解决了 HashMap 多线程并发不安全、Hashtable 效率低的问题,JDK7 和 JDK8 的实现完全不同:
✅ JDK7 实现:分段锁(Segment)
底层结构:Segment 数组 + HashEntry 数组 + 链表 ,每个 Segment 是一个独立的分段锁;
线程安全:对 每个 Segment 加锁 ,不同分段的线程可以并发操作,锁的粒度是 Segment ;
缺点:Segment 的数量固定(默认 16),并发度有限,哈希冲突时性能下降。
✅ JDK8 实现:CAS + synchronized + 红黑树
底层结构:数组 + 链表 + 红黑树 ,和 HashMap 结构一致;
线程安全:锁的粒度是数组的单个节点 ,是极致的细粒度锁,并发性能拉满;
无元素时:用 CAS 插入元素,无锁;
有元素时:对 数组的单个节点(链表头 / 红黑树根节点)加 synchronized 锁 ,只锁住当前节点,其他节点的线程可以并发操作;
优点:并发度极高,性能远高于 JDK7,是目前并发场景的首选。
四、Spring & SpringBoot 全家桶(高级开发必问,核心考点)
1. Spring IoC 是什么?核心原理?IoC 容器的作用? 答:IoC(Inverse of Control,控制反转 )是 Spring 的核心思想,也是 Spring 的基石;
传统开发 :程序员手动创建对象、管理对象的依赖关系(比如 A 依赖 B,程序员手动 new B,再注入到 A 中),控制权在程序员手里;
IoC 开发 :对象的创建、依赖注入、生命周期管理,全部交给 Spring IoC 容器 负责,程序员只需要定义对象,控制权反转到容器,这就是控制反转。
DI(依赖注入) :是 IoC 的 具体实现方式 ,指容器在创建对象时,自动将依赖的对象注入到当前对象中,常用的注入方式:构造器注入(推荐)、setter 注入、字段注入。
IoC 容器的核心作用 :① 统一创建和管理对象,降低对象之间的耦合度;② 自动注入依赖,简化开发;③ 管理对象的生命周期(初始化、销毁);④ 提供切面编程(AOP)、事务管理等扩展功能。
2. Spring AOP 是什么?核心原理?应用场景?(必考) 答:AOP(Aspect Oriented Programming,面向切面编程 ),是 Spring 的两大核心之一,和 OOP(面向对象)互补;OOP 是纵向的,把业务拆分为「对象」;AOP 是横向的,把 重复的、通用的逻辑(切面) 抽离出来,比如日志、事务、权限校验、异常处理,在不修改业务代码的前提下,动态织入到业务方法中,实现「无侵入式增强」。
✅ 核心原理 Spring AOP 的底层是 JDK 动态代理 + CGLIB 动态代理 ,自动选择代理方式:
如果目标对象 实现了接口 ,默认使用 JDK 动态代理 :基于接口生成代理类,代理类和目标类实现同一个接口,调用代理方法时增强逻辑;
如果目标对象 没有实现接口 ,使用 CGLIB 动态代理 :基于目标类生成子类,子类重写目标方法,在重写的方法中增强逻辑;
✅ 核心术语(必背)
切面(Aspect):抽离的通用逻辑(比如日志切面、事务切面);
切点(Pointcut):哪些方法需要被增强(比如所有 service 的方法);
通知(Advice):增强的具体逻辑,分 5 种:前置通知、后置通知、返回通知、异常通知、环绕通知(最常用);
织入(Weaving):将切面逻辑动态植入到目标方法的过程。
✅ 应用场景 日志记录、事务管理、权限校验、异常统一处理、接口限流、缓存控制等。
3. Spring 事务的传播机制和隔离级别(顶级高频,必背)
✅ 事务的隔离级别(解决并发事务的问题) 数据库的事务隔离级别是基础,Spring 完全兼容数据库的隔离级别,共 4 种,从低到高:
READ_UNCOMMITTED(读未提交) :最低级别,允许读取其他事务未提交的数据,存在 脏读、不可重复读、幻读 ;
READ_COMMITTED(读已提交) :主流(MySQL 默认),只能读取其他事务已提交的数据,解决 脏读 ,存在 不可重复读、幻读 ;
REPEATABLE_READ(可重复读) :MySQL InnoDB 默认,同一个事务中多次读取同一数据,结果一致,解决 脏读、不可重复读 ,存在 幻读 ;
SERIALIZABLE(串行化) :最高级别,事务串行执行,无并发问题,解决所有问题,但性能极差,几乎不用。
并发事务的三大问题 :脏读(读了未提交的数据)、不可重复读(同一事务多次读同一数据,结果不同)、幻读(同一事务多次查询,结果条数不同)。
✅ 事务的传播机制(核心,必背,7 种,常用 3 种) 事务的传播机制定义:当一个事务方法调用另一个事务方法时,事务如何传递 ,Spring 共定义了 7 种传播机制,都是 Propagation 枚举值,常用 3 种,必须背熟 :
REQUIRED(默认) :如果当前有事务,就加入该事务;如果没有,就新建一个事务;最常用 (比如 service 的方法调用);
REQUIRES_NEW :无论当前是否有事务,都 新建一个独立的事务 ,新事务和原事务互不影响;比如:下单成功后记录日志,日志的事务失败不影响下单的事务;
NESTED :嵌套事务,如果当前有事务,就嵌套在当前事务中;如果没有,就新建一个事务;子事务失败,父事务可以回滚子事务,不影响父事务;
SUPPORTS :支持当前事务,没有则以非事务执行;
NOT_SUPPORTED :以非事务执行,有则挂起当前事务;
MANDATORY :必须在事务中执行,否则抛出异常;
NEVER :必须在非事务中执行,否则抛出异常。
4. SpringBoot 自动装配原理(大厂必问,加分项) 答:SpringBoot 的核心卖点就是「自动装配」,即 无需手动配置,自动加载依赖的组件和配置 ,原理核心围绕 3 个核心注解 + 1 个核心文件 :
✅ 核心注解(启动类上的注解)
@SpringBootApplication:是一个组合注解,核心包含 3 个注解:
@SpringBootConfiguration:本质是 @Configuration,标记启动类为配置类;
@EnableAutoConfiguration:自动装配的核心注解 ,开启自动装配;
@ComponentScan:扫描当前包及其子包的 @Component 注解的类(Controller、Service、Repository 等);
@EnableAutoConfiguration 的核心逻辑:
底层通过 AutoConfigurationImportSelector 类,扫描 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件 ;
该文件中定义了所有自动配置类(比如 DataSourceAutoConfiguration、WebMvcAutoConfiguration);
SpringBoot 会根据 依赖的 jar 包 ,自动加载对应的自动配置类,完成组件的自动装配;
✅ 核心规则 按需装配 :只有当项目中引入了对应的依赖 jar 包,对应的自动配置类才会生效;比如引入 spring-webmvc,WebMvcAutoConfiguration 就生效;引入 mysql-connector,DataSourceAutoConfiguration 就生效。
五、MySQL 高级(必问,优化 + 锁 + 索引,核心考点)
1. MySQL 索引的类型?为什么索引能提升查询效率?失效场景?
✅ 索引的核心类型(必背)
按存储结构:聚簇索引(主键索引) 、非聚簇索引(辅助索引) ;
聚簇索引:叶子节点存储 整行数据 ,一张表只能有一个聚簇索引,默认是主键索引,无主键则选唯一索引,无唯一索引则自动生成隐藏主键;
非聚簇索引:叶子节点存储 主键值 ,查询时需要回表(通过主键值查聚簇索引的整行数据);
按功能:主键索引、唯一索引、普通索引、联合索引(复合索引)、全文索引;
按字段特性:单列索引、联合索引(最常用,优化查询的核心)。
✅ 索引为什么能提升查询效率? 索引的底层是 B + 树 数据结构,B + 树是一种平衡多路查找树,特点:① 所有数据都在叶子节点,叶子节点按顺序排列,支持范围查询;② 树的高度极低(百万级数据,树高仅 3-4 层),查询时只需 3-4 次磁盘 IO,磁盘 IO 是数据库查询的核心瓶颈 ,所以索引能极大提升查询效率;③ 无索引的查询是全表扫描(O(n)),有索引的查询是树的二分查找(O(log n))。
✅ 索引失效的高频场景(必背,优化 SQL 的核心)
联合索引 违反最左匹配原则 (最核心);
索引列上做 函数运算、表达式运算、类型转换 (比如 where age+1=20、where name like '%张三');
使用 or 连接的条件,有一个字段无索引,则所有索引失效;
like 模糊查询以 % 开头(%张三),索引失效;以 张三% 开头,索引有效;
数据分布不均,比如 where status=1,status 的取值只有 0 和 1,且 1 占 99%,索引失效;
is null 会走索引,is not null 大概率失效。
2. MySQL 锁的分类?行锁和表锁的区别?
✅ 核心锁分类
按粒度分:表锁 (锁整张表)、行锁 (锁单行数据)、页锁 (介于表锁和行锁之间);
按兼容性分:共享锁(读锁) 、排他锁(写锁) ;
共享锁:多个线程可以同时加读锁,读锁之间兼容,读锁和写锁互斥;
排他锁:只有一个线程可以加写锁,写锁之间互斥,写锁和读锁互斥;
✅ 行锁 vs 表锁(必考)
表锁 :MySQL MyISAM 引擎默认,锁粒度大,加锁快,冲突概率高,并发性能差;适合 读多写少 的场景;
行锁 :MySQL InnoDB 引擎默认,锁粒度小,加锁慢,冲突概率低,并发性能好;适合 写多读少 的场景;✔ 核心:InnoDB 的行锁是 基于索引的 ,如果查询没有走索引,行锁会升级为表锁!
3. MySQL 慢查询优化的核心步骤(标准答案,必背) 答:这是 DBA 和 Java 高级开发的必考问题,优化步骤固定,分 5 步,按顺序执行:
开启慢查询日志 :配置 slow_query_log=ON,设置慢查询阈值(比如 long_query_time=1,执行时间 > 1 秒的 SQL 为慢查询),记录所有慢查询 SQL;
分析慢查询 SQL :用 explain 关键字分析 SQL 执行计划,重点看 type(查询类型)、key(使用的索引)、rows(扫描的行数)、Extra(额外信息);
优化索引 :给查询条件、联表条件添加索引,优先创建联合索引,避免冗余索引,删除无用索引;
优化 SQL 语句 :避免索引失效的写法,避免大表联表查询,避免 select *,分页查询优化,子查询改联表查询;
优化表结构 :分库分表(大表)、垂直拆分(字段过多)、水平拆分(数据量过大),合理设置字段类型。
六、最后:高频加分题(拉开差距,大厂必问)
1. 分布式理论:CAP 定理和 BASE 理论 答:CAP 是分布式系统的 铁律 ,BASE 是 CAP 的妥协方案,所有分布式框架(Dubbo、SpringCloud)都基于这两个理论设计。
CAP 定理 :一个分布式系统,最多只能同时满足一致性(C)、可用性(A)、分区容错性(P)中的两个 ,三者不可兼得;✔ 一致性:所有节点的数据一致;✔ 可用性:服务始终可用,响应时间正常;✔ 分区容错性:网络分区时,系统仍能正常工作;✔ 结论:分布式系统 必须保证 P ,所以只能在 CP 和 AP 之间选择:比如 ZooKeeper 是 CP,Eureka 是 AP。
BASE 理论 :是对 CAP 中 AP 的延伸,核心是「最终一致性 」,牺牲强一致性,换取高可用性,是分布式系统的实际解决方案;✔ 核心:基本可用、软状态、最终一致性。
2. 设计模式:常用的设计模式及应用场景(必背 5 种) Java 高级开发必须掌握设计模式,面试官会问「你项目中用过哪些设计模式?」,以下是高频必考的 5 种:
单例模式 :保证一个类只有一个实例,应用:Spring IoC 容器的 Bean 默认是单例、工具类;
工厂模式 :封装对象的创建逻辑,应用:Spring 的 BeanFactory、MyBatis 的 SqlSessionFactory;
代理模式 :增强对象的功能,应用:Spring AOP 的动态代理、MyBatis 的 Mapper 代理;
装饰器模式 :动态给对象添加功能,应用:Java IO 的 BufferedReader、BufferedWriter;
观察者模式 :发布 - 订阅模式,应用:Spring 的事件监听机制、消息队列;
3. 项目中遇到的最大的技术难题?如何解决的?(必问,面试压轴) 这是面试官的 压轴题 ,核心考察你的问题解决能力,回答思路:问题背景 → 问题现象 → 排查过程 → 解决方案 → 总结反思 ,一定要结合自己的项目实际说,比如:
问题:生产环境接口响应慢,偶尔超时,CPU 利用率高;
排查:用 Arthas 分析线程栈,发现大量线程阻塞在 synchronized 锁;用 JProfiler 分析堆内存,发现内存泄漏;
解决:把 synchronized 换成 ReentrantLock,手动释放锁;修复内存泄漏的代码,添加内存监控;
总结:以后写并发代码要注意锁的粒度,线上环境一定要加监控。
总结 以上是 Java 高级面试的 全部核心高频题 ,覆盖了所有必考模块,答案都是「面试标准答案」。Java 高级面试的核心考察点:JVM 调优、并发编程的深度理解、集合源码、Spring 核心原理、MySQL 优化 ,这几块吃透,面试通过率会大幅提升。最后提醒:面试时不要只背答案,要 理解原理 ,面试官会根据答案继续深挖,比如问完 HashMap 会问 ConcurrentHashMap,问完 synchronized 会问 Lock,理解后才能应对所有追问。
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online