跳到主要内容Java 高级工程师高频核心面试题 | 极客日志JavaNode.jsAIjava算法
Java 高级工程师高频核心面试题
这份内容系统梳理了 Java 高级面试的高频核心题,覆盖 JVM 内存结构与 GC、并发编程中的锁、volatile、CAS、ThreadLocal,集合源码里的 HashMap 与 ConcurrentHashMap,Spring IoC/AOP/事务/自动装配,以及 MySQL 索引、锁和慢查询优化等重点。整体答案强调原理、边界和常见追问,适合中高级开发面试准备与口述复习。
FlinkHero1 浏览 一、JVM 虚拟机(重中之重,必问,分值最高)
1. JVM 内存结构(运行时数据区),JDK 8 做了什么重大改动?
答:JVM 运行时数据区包含 方法区、堆、虚拟机栈、本地方法栈、程序计数器 5 个区域。其中,虚拟机栈、本地方法栈、程序计数器是线程私有的;堆和方法区是线程共享的。
- 程序计数器:记录当前线程执行到哪一条字节码指令,是唯一不会 OOM 的区域;
- 虚拟机栈:每个方法执行时都会创建栈帧,存储局部变量表、操作数栈等。方法调用时入栈,执行结束后出栈,栈深度过大时会抛出
StackOverflowError;
- 堆:JVM 中最大的内存区域,也是 GC 的主要回收区域,用于存放对象实例和数组。堆内存不足时会抛出
OutOfMemoryError: Java heap space;
- 本地方法栈:为 native 方法服务,和虚拟机栈的原理类似,也可能出现栈溢出或 OOM;
- 方法区:用于存储类信息、常量、静态变量、即时编译后的代码等。对应的内存不足时,JDK 8 之后通常表现为
OutOfMemoryError: Metaspace。
JDK 8 的核心改动:
- 取消永久代(PermGen),改为 元空间(Metaspace);
- 元空间不再位于 JVM 堆内,而是使用 本地内存;
- 字符串常量池、静态变量等从永久代迁移到了 堆内存;
- 这样做的主要原因,是永久代容量固定,比较容易 OOM,而元空间受本机可用内存限制,扩展性更好。
2. 垃圾回收:哪些内存需要回收?什么时候回收?怎么回收?
哪些内存需要回收?
需要 GC 处理的主要是 堆内存 和 方法区(元空间)。虚拟机栈、本地方法栈、程序计数器会随着线程结束自动释放,不属于 GC 的重点。
核心目标是:回收堆中不再被引用的对象,以及方法区中无用的常量和类信息。
什么时候回收?
1)Minor GC
新生代(Eden 区 + Survivor 区)中,通常是 Eden 区满了就触发 Minor GC。它发生频率高,但回收对象大多是朝生夕死的,速度也快。
2)Major GC / Full GC
常见触发场景如下:
- 老年代空间不足;
- Minor GC 后,存活对象无法放入 Survivor 区,需要晋升到老年代,但老年代空间不够;
- 方法区(元空间)空间不足;
- 调用
System.gc(),JVM 大概率会触发 Full GC,但这只是建议,不是强制;
- 分配大对象时,新生代和老年代都放不下。
怎么回收?
1)标记-清除
先标记存活对象,再清除未标记对象。缺点是会产生内存碎片,而且执行效率一般。
2)复制算法
把内存分成两块,每次只使用其中一块;当这块内存用完后,把存活对象复制到另一块,原内存整体清空。新生代就是基于这种思路设计的。它的优点是无碎片、效率高,缺点是浪费一部分内存空间。
3)标记-整理
先标记存活对象,再把存活对象向一端移动,最后清理边界之外的空间。老年代常用这种思路。优点是没有碎片,缺点是移动对象的成本比较高。
4)分代收集
这是 JVM 默认采用的组合思路:新生代用复制算法,老年代用标记-整理或标记-清除。按对象生命周期分区处理,是实际工程里最常见的方案。
垃圾收集器
JDK 8 默认组合:Parallel GC + Parallel Old GC,偏向吞吐量,适合后台计算、大数据分析等场景;JDK 9 及以后默认组合:G1 GC,兼顾吞吐量和停顿时间,是当前比较主流的选择;高并发、低停顿场景常见的是 CMS + ParNew,但它更偏向响应时间,代价是 CPU 开销和碎片问题;ZGC、Shenandoah 这类收集器则更适合超大内存、低停顿需求场景。3. 如何判断一个对象是否存活?
JVM 判断对象是否存活,核心看它是否还能从根对象追踪到。
- 引用计数法:对象被引用时计数 +1,引用失效时计数 -1,计数为 0 就回收。问题是它解决不了循环引用,所以 JVM 不采用这个方法;
- 可达性分析算法:从一组 GC Roots 出发,沿着引用链向下搜索。如果某个对象和 GC Roots 之间没有任何引用链相连,就认为它不可达,可以回收。
- 虚拟机栈中引用的对象;
- 本地方法栈中 native 方法引用的对象;
- 方法区中静态变量或常量引用的对象。
4. Java 中的四种引用类型
从强到弱依次是:强引用 > 软引用 > 弱引用 > 虚引用。它们的差异主要体现在 GC 回收的时机上。
- 强引用:最常见的引用方式,例如
Object obj = new Object()。只要强引用还在,对象就不会被回收;
- 软引用:内存充足时不会回收,内存紧张、即将 OOM 时才回收,常用于缓存;
- 弱引用:只要发生 GC,就会被回收,常见于
WeakHashMap 这类场景;
- 虚引用:最弱,无法通过它直接获取对象实例,更多用于对象回收时的通知和堆外内存管理。
5. JVM 调优核心问题
调优目标
- 尽量减少 Full GC 次数;
- 控制 Minor GC 停顿时间。
如果业务更看重吞吐量,就偏向吞吐量优化;如果更看重响应时间,就要控制停顿。
常用 JVM 参数
-Xms1024m
-Xmx1024m
-Xmn512m
-XX:SurvivorRatio=8
-XX:+UseParallelGC
-XX:+UseParallelOldGC
-XX:+UseG1GC
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./dump.hprof
调优步骤
- 线上环境开启必要的 JVM 参数,尤其是 OOM 时自动生成堆转储;
- 使用 MAT、JProfiler、VisualVM 等工具分析堆转储文件,找到内存泄漏或异常对象;
- 根据分析结果调整堆大小、新生代比例和垃圾收集器;
- 回到代码层面修复内存泄漏问题,比如关闭连接、清理无用集合、避免静态集合持有大量对象;
- 最后通过压测验证调整结果。
二、Java 并发编程(核心必考,占比很高)
1. 进程和线程的区别?为什么要用多线程?
- 进程:操作系统分配资源的最小单位,拥有独立内存空间;
- 线程:CPU 调度和执行的最小单位,是进程中的执行流,线程之间共享进程资源,但各自有独立的栈和计数器。
- 提升 CPU 利用率,适合多核并行;
- 提升程序响应速度,避免耗时任务阻塞主流程;
- 拆分复杂业务,让程序结构更清晰。
2. 线程的生命周期
Java 线程状态定义在 Thread.State 中,常见的 6 种状态是:
NEW → RUNNABLE → BLOCKED / WAITING / TIMED_WAITING → TERMINATED
- NEW:线程对象创建了,但还没调用
start();
- RUNNABLE:调用
start() 后进入该状态,包含就绪和运行两种语义;
- BLOCKED:竞争
synchronized 锁失败,进入阻塞;
- WAITING:调用
wait()、join()、LockSupport.park() 后进入,无超时;
- TIMED_WAITING:调用带超时的方法后进入,例如
sleep(long)、wait(long);
- TERMINATED:线程执行完毕或异常退出。
3. synchronized 和 Lock 的区别
-
底层实现
synchronized 是 Java 关键字,由 JVM 实现;
Lock 是接口,常见实现是 ReentrantLock,底层基于 AQS。
-
锁的释放
synchronized 会自动释放锁;
Lock 必须手动在 finally 中调用 unlock()。
-
获取方式
synchronized 获取失败时只能阻塞等待;
Lock 支持可中断、可超时、尝试获取锁。
-
锁特性
synchronized 是可重入、非公平锁;
ReentrantLock 也可重入,并且可以手动选择公平或非公平。
-
扩展能力
简单场景下,用 synchronized 更省心;需要超时、中断、公平性控制时,Lock 更合适。
4. 什么是死锁?怎么避免?
死锁指的是:多个线程互相持有对方需要的锁,又都不释放自己的锁,最终彼此卡住,谁也无法继续执行。
避免死锁,关键就是想办法破坏其中一个条件。工程里最常见、也最稳妥的做法是:统一加锁顺序。此外,减少锁持有时间、使用 tryLock()、尽量缩小锁粒度,也都很有效。
5. volatile 的作用是什么?为什么它能保证可见性和有序性,却不能保证原子性?
volatile 是一种轻量级同步机制,核心作用有两个:保证可见性 和 禁止指令重排序。
- 可见性:一个线程修改了
volatile 变量,其他线程会立即看到最新值;
- 有序性:编译器和 CPU 不会随意重排
volatile 变量前后的指令;
- 原子性:
volatile 不能保证。
原因很简单:volatile 本质上没有加锁。像 count++ 这种复合操作,实际包含读取、修改、写入三个步骤,期间可能被其他线程打断,所以结果不可靠。要解决原子性问题,通常使用 AtomicInteger 这类原子类,或者直接使用锁。
6. CAS 是什么?ABA 问题怎么解决?
CAS(Compare And Swap)是无锁编程里非常核心的一种思想。它的逻辑很简单:如果内存中的值等于预期值,就更新成新值;否则什么都不做。整个过程由 CPU 指令保证原子性。
CAS 的优点是并发性能好,不需要加锁;缺点也很明显:
- 可能存在 ABA 问题;
- 失败时会自旋重试,CPU 开销较高;
- 只能保证单个变量的原子性。
ABA 问题
线程 A 看到值是 1,准备 CAS 更新为 2;在它执行之前,线程 B 先把 1 改成 3,又改回 1。此时线程 A 再执行 CAS,会误以为值没有变过。
解决办法
最常见的做法是 给值加版本号。每次修改时,版本号一起递增,CAS 时同时比较值和版本号。Java 里常见的实现有 AtomicStampedReference 和 AtomicMarkableReference。
7. ThreadLocal 是什么?为什么会有内存泄漏问题?
ThreadLocal 的作用是:给每个线程保存一份独立的变量副本,线程之间互不干扰。它经常用来解决线程安全问题,属于典型的空间换时间方案。
底层原理
每个线程内部都维护着一个 ThreadLocalMap:
key 是 ThreadLocal 对象本身,而且是弱引用;
value 是当前线程保存的变量副本。
线程调用 set() 时,会把值放进自己的 ThreadLocalMap;调用 get() 时,也只从当前线程的 map 里取。
为什么会内存泄漏?
问题出在这里:key 是弱引用,value 却是强引用。ThreadLocal 对象一旦没有外部强引用,GC 会把 key 回收掉,但 value 还挂在 ThreadLocalMap 里。如果线程长期存在,比如线程池里的核心线程,这个 value 就一直无法释放,久而久之就会造成内存泄漏。
怎么避免?
用完 ThreadLocal 后,一定记得手动调用 remove()。这一步很关键,不能省。
三、Java 集合源码(高频必考,HashMap 是重点)
1. HashMap JDK 7 vs JDK 8 的核心区别
HashMap 在 JDK 8 里做了比较大的优化,面试时很容易追问到细节。
-
底层结构
- JDK 7:数组 + 链表;
- JDK 8:数组 + 链表 + 红黑树。
-
链表插入方式
- JDK 7:头插法;
- JDK 8:尾插法,避免了扩容时链表反转带来的问题。
-
树化条件
- 链表长度达到一定阈值后,如果数组容量也足够大,就会转成红黑树;
- 反过来,当链表变短时,又会退回链表。
-
扩容方式
- JDK 8 扩容时,元素的新位置要么不变,要么是原索引加上原数组长度,效率更高。
-
初始化方式
- JDK 8 是懒加载,第一次
put 时才真正初始化数组。
2. HashMap 为什么容量总是 2 的幂次?
- 计算快:
hash & (length - 1) 比取模运算更快;
- 分布更均匀:2 的幂次能让哈希值更多位参与运算,减少冲突。
3. ConcurrentHashMap 线程安全是怎么实现的?
JDK 7:分段锁
JDK 7 采用 Segment 分段锁的方式,把整体数组拆成多个段,不同段可以并发操作。优点是实现简单,缺点是并发度受限。
JDK 8:CAS + synchronized + 红黑树
- 空桶插入时用 CAS;
- 桶内已经有元素时,对当前桶的头节点加
synchronized;
- 冲突链过长时转红黑树。
四、Spring 与 Spring Boot 全家桶
1. Spring IoC 是什么?
IoC(Inverse of Control,控制反转)说白了,就是把对象的创建和依赖关系管理,从程序员手里交给 Spring 容器。
传统写法里,A 依赖 B,往往是自己 new B();而在 IoC 模式下,容器负责创建 B,再把它注入给 A。
DI(依赖注入)是 IoC 的具体实现方式,常见注入方式有构造器注入、setter 注入和字段注入。
IoC 容器的价值主要体现在:降低耦合、统一管理对象生命周期、简化依赖装配,并为 AOP、事务等能力提供基础。
2. Spring AOP 是什么?
AOP(面向切面编程)解决的是'横切关注点'问题,比如日志、事务、权限校验、异常处理,这些逻辑往往会散落在很多业务方法里。AOP 的思路,是把它们抽出来统一处理,再在运行时织入到目标方法中。
底层原理
Spring AOP 通常会根据目标对象是否实现接口,自动选择代理方式:
- 实现了接口,默认走 JDK 动态代理;
- 没有接口,则使用 CGLIB 生成子类代理。
核心术语
- 切面(Aspect):通用逻辑本身;
- 切点(Pointcut):哪些方法需要增强;
- 通知(Advice):具体的增强动作;
- 织入(Weaving):把增强逻辑应用到目标方法的过程。
3. Spring 事务的传播机制和隔离级别
事务隔离级别
- READ_UNCOMMITTED:能读到未提交数据,会有脏读、不可重复读、幻读;
- READ_COMMITTED:只能读到已提交数据,解决脏读;
- REPEATABLE_READ:同一事务中多次读同一数据,结果一致,是 MySQL InnoDB 的默认级别;
- SERIALIZABLE:事务串行执行,问题最少,但并发性能最差。
并发事务常说的三大问题是:脏读、不可重复读、幻读。
事务传播机制
事务传播机制,讲的是一个事务方法调用另一个事务方法时,事务怎么传递。常见的有 7 种,这里重点记住 3 种:
- REQUIRED:默认值,有事务就加入,没有就新建;
- REQUIRES_NEW:无论如何都新建一个独立事务;
- NESTED:嵌套事务,子事务失败可以回滚自己,不一定影响父事务。
其他几种像 SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER,了解即可。
4. Spring Boot 自动装配原理
Spring Boot 的核心价值就是自动装配,尽量减少手工配置。
启动类上的 @SpringBootApplication 本身是一个组合注解,里面最关键的三个部分是:
@SpringBootConfiguration:本质上就是配置类;
@EnableAutoConfiguration:开启自动装配;
@ComponentScan:扫描组件。
自动装配的关键逻辑,是通过 AutoConfigurationImportSelector 去加载自动配置文件,再根据当前项目中引入的依赖,决定哪些配置类需要生效。也就是说,引了什么依赖,就装配什么能力。
五、MySQL 高级
1. MySQL 索引的类型?为什么索引能提升查询效率?
常见索引类型
- 聚簇索引:叶子节点存整行数据,一张表通常只有一个,主键索引默认就是它;
- 非聚簇索引:叶子节点存主键值,查询时可能需要回表;
- 从功能上还可以分为:主键索引、唯一索引、普通索引、联合索引、全文索引等。
为什么索引快?
- 树高低,查找时磁盘 IO 次数少;
- 叶子节点天然有序,适合范围查询;
- 相比全表扫描,查询效率高得多。
常见索引失效场景
- 联合索引违反最左匹配原则;
- 索引列上做函数、表达式或类型转换;
or 条件中有一边没有索引;
like '%xxx' 这种前缀模糊查询;
- 数据区分度太低,索引收益不大;
- 某些
is not null 场景下索引效果不好。
2. MySQL 锁的分类?行锁和表锁有什么区别?
按粒度分,锁可以分成表锁、行锁和页锁;按兼容性分,可以分成共享锁和排他锁。
- 表锁:锁粒度大,加锁快,但并发差;
- 行锁:锁粒度小,并发好,但加锁和管理成本更高。
InnoDB 默认使用行锁,但要注意,行锁是基于索引的。如果 SQL 没走索引,锁的范围可能会扩大,性能也会明显下降。
3. MySQL 慢查询优化怎么做?
- 先开启慢查询日志;
- 用
explain 看执行计划,重点关注 type、key、rows 和 Extra;
- 给高频查询和关联条件补合适的索引;
- 调整 SQL 写法,避免索引失效,尽量减少不必要的全表扫描;
- 业务量继续上升时,再考虑分库分表、垂直拆分或水平拆分。
六、加分题:分布式、设计模式和项目追问
1. CAP 定理和 BASE 理论
CAP 是分布式系统的基础约束,意思是:一个系统不可能同时完全满足一致性(C)、可用性(A)和分区容错性(P)。在实际分布式环境里,P 基本是必须保证的,所以工程上通常是在 CP 和 AP 之间权衡。
BASE 可以看作对 CAP 的现实妥协,核心思想是 最终一致性。它强调基本可用、软状态和最终一致性,适合大多数实际业务系统。
2. 常见设计模式及场景
Java 高级开发常被问到'项目里用过哪些设计模式',下面这几个最常见:
- 单例模式:保证一个类只有一个实例;
- 工厂模式:统一创建对象,隐藏创建细节;
- 代理模式:在不修改原对象的前提下增强功能;
- 装饰器模式:动态给对象叠加能力;
- 观察者模式:一处发布,多处订阅。
3. 项目里遇到的最大技术难题怎么回答?
这类题没有标准模板,但回答思路最好保持清晰:问题背景、现象、排查过程、解决方案、复盘总结。
比如线上接口偶发超时,可以从线程阻塞、锁竞争、GC 停顿、内存泄漏这几个方向排查;如果最后是锁粒度过大,那就要说明你怎么缩小锁范围、如何验证效果、如何补监控。
总结
Java 高级面试的核心,基本都绕不开 JVM、并发、集合、Spring、MySQL 这五块。真正拉开差距的,不是能不能背出答案,而是能不能顺着问题继续讲下去,讲清原理、讲透边界、讲明白为什么。面试时,把这些点说稳了,基本就已经赢了一半。
相关免费在线工具
- 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
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online