✅ 适配人群:中高级 java 开发 / 资深开发 / 技术专家,校招拔高 / 社招跳槽通用,覆盖大厂 95% 高频考点
✅ 内容分级:java核心进阶(25%) → 并发编程核心(30%) → jvm虚拟机深度(20%) → 分布式&框架源码(15%) → 项目架构&实战方案(10%)
✅ 答案特点:全部是面试高分精简版,分点清晰、突出核心考点 + 坑点 + 加分项,无冗余内容,背完直接用,兼顾原理 + 实战 + 调优,直击高薪面试核心
一、java 核心进阶(高级开发必问,基础拔高,25%)
1. hashmap jdk7 vs jdk8 底层实现及核心区别?【必考・高频天花板】
答案要点(面试满分版,分点必背)
- jdk7 底层结构:数组 + 链表,哈希表采用
拉链法解决哈希冲突。 - jdk8 底层结构:数组 + 链表 + 红黑树,是 jdk7 的优化版,核心区别如下:
- 哈希冲突解决:当链表长度 >=8 且 数组长度 >=64 时,链表转为红黑树;红黑树节点数 <=6 时,退化为链表。(优化原因:链表查询 o (n),红黑树查询 o (logn),解决长链表查询慢问题)
- 哈希算法优化:jdk8 的哈希值计算更高效,减少哈希碰撞概率。
- 扩容机制优化:jdk7 扩容是头插法,并发下会导致链表成环、数据丢失;jdk8 扩容是尾插法,并发下不会成环,且扩容时红黑树会拆分为链表 / 红黑树。
- put 方法流程:jdk7 先扩容后插入;jdk8 先插入后扩容。
- ✅ 核心面试坑点:hashmap 是线程不安全的,并发场景下会出现死循环、数据覆盖,解决方案:使用
concurrenthashmap替代。 - ✅ 加分回答:hashmap 默认初始容量 16,负载因子 0.75,扩容是 2 倍扩容;初始容量必须是 2 的幂次,目的是通过位运算替代取模,提升哈希寻址效率。
2. concurrenthashmap jdk7 vs jdk8 实现原理及线程安全保证?【必考】
答案要点
核心结论:concurrenthashmap 是 hashmap 的线程安全版,高并发场景下的首选 map,jdk7 和 jdk8 实现完全不同,面试必问两者区别
jdk7 实现:分段锁(segment)+ 数组 + 链表
- 底层是一个 segment 数组,每个 segment 是一个 hashmap,segment 继承了 reentrantlock;
- 线程安全:通过分段锁机制,只锁住当前操作的 segment,其他 segment 可并发操作,锁粒度是segment 级别;
- 缺点:分段锁的粒度还是偏大,并发量高时性能一般。
jdk8 实现:cas + synchronized + 数组 + 链表 + 红黑树
- 彻底抛弃分段锁,底层和 hashmap 结构一致(数组 + 链表 + 红黑树);
- 线程安全:双重加锁机制
- ✔ 无冲突时:用
cas原子操作保证并发安全,无锁开销,性能极高; - ✔ 有冲突时:用
synchronized锁住当前哈希桶的首节点,锁粒度是节点级别(jdk8 的 synchronized 做了重量级锁→轻量级锁→偏向锁优化,性能不输 reentrantlock);
- ✔ 无冲突时:用
- 优点:锁粒度极小,并发性能远超 jdk7 版本,是生产环境的最优解。
3. arraylist 和 linkedlist 的区别?扩容机制?为什么 arraylist 查询快、增删慢?【高频】
答案要点
✔ 核心区别(底层 + 性能 + 适用场景)
- 底层结构:
arraylist是动态数组,基于数组实现;linkedlist是双向链表,基于链表实现。 - 性能差异(核心):
- 查询 / 随机访问:arraylist 快(数组下标寻址,o (1)),linkedlist 慢(链表遍历,o (n));
- 增删操作:arraylist 慢(数组扩容 + 元素移位,o (n)),linkedlist 快(链表节点修改指针,o (1));
- 注意:arraylist 的尾部增删是 o (1),效率和 linkedlist 持平。
- 内存占用:arraylist 内存连续,浪费少量空间(扩容预留);linkedlist 每个节点存储数据 + 指针,内存开销更大。
- 适用场景:读多写少用 arraylist,写多读少用 linkedlist。
✔ arraylist 扩容机制
- 初始容量:无参构造 jdk7 是 10,jdk8 是懒加载,第一次 add 时才初始化容量为 10;
- 扩容阈值:当元素个数 >= 容量
- 扩容规则:默认扩容为原容量的 1.5 倍,扩容时会创建新数组,复制原数组元素,是耗时操作;
- 优化建议:提前指定初始容量
new arraylist<>(1000),避免频繁扩容。
4. equals 和 hashcode 的关系?为什么重写 equals 必须重写 hashcode?【必考・坑点】
答案要点(面试标准答案,背会即可)
默认实现(object 类):
- equals:比较两个对象的内存地址,只有同一对象才返回 true;
- hashcode:返回对象的内存地址哈希值,同一对象的 hashcode 一定相同。
核心约定(java 官方规定,必须遵守):
✔ 若
a.equals(b) = true,则a.hashcode()必须等于b.hashcode();✔ 若
a.hashcode() = b.hashcode(),则a.equals(b)不一定为 true(哈希碰撞);✔ 若
a.equals(b) = false,则a.hashcode()可以相同 / 不同。
为什么重写 equals 必须重写 hashcode?【核心】
- 场景:当对象存入
hashmap/hashset等哈希集合时,会先通过 hashcode 定位哈希桶,再通过 equals 比较元素; - 后果:只重写 equals 不重写 hashcode,会导致两个逻辑相等的对象,hashcode 不同,存入哈希集合时会被当成两个不同元素,出现数据重复,违背集合的去重原则。
- 场景:当对象存入
5. java8 核心新特性有哪些?为什么说 java8 是里程碑式的版本?【必考】
✅ 核心:java8 是 java 史上最重要的版本,新增特性解决了 java 函数式编程、并发效率、代码简洁性的痛点,高级开发必须吃透,回答时分点说核心特性 + 应用场景答案要点(高频必答的 6 个核心特性)
- lambda 表达式:简化匿名内部类的写法,实现函数式编程,代码更简洁,如
list.foreach(x -> system.out.println(x)); - 函数式接口:只有一个抽象方法的接口(如 consumer、supplier、function),是 lambda 的基础,配合注解
@functionalinterface; - stream 流式编程:对集合进行高效的聚合操作(过滤、映射、分组、统计),支持串行 / 并行,大幅简化集合处理代码,性能远超传统 for 循环;
- optional 类:解决空指针异常 (npe) 的终极方案,通过链式调用避免层层判空,如
optional.ifpresent().orelse().orelseget(); - 默认方法 & 静态方法:接口中可以定义带实现的
default方法和static方法,解决接口的版本兼容问题(新增方法不影响实现类); - 时间日期 api:java.time 包下的 localdatetime、localdate 等,线程安全、api 友好,彻底替代线程不安全的 simpledateformat/date。
- 加分项:还新增了 completablefuture(异步编程)、方法引用、重复注解等特性。
6. 泛型的作用?什么是泛型擦除?有什么问题?【高频】
答案要点
- 泛型的核心作用:① 编译期类型安全检查,避免类型转换错误;② 消除代码中的强制类型转换,代码更简洁。
- 泛型擦除:java 的泛型是伪泛型,泛型信息只在编译期有效,编译后字节码文件中会擦除泛型类型,替换为原生类型(object),运行时无法获取泛型的具体类型。
- 泛型擦除带来的问题:
- ✔ 无法获取泛型的 class 对象,如
list<string>.class编译报错; - ✔ 无法创建泛型数组,如
new arraylist<string>[10]编译报错; - ✔ 泛型方法的重载可能失效,因为擦除后类型相同。
- ✔ 无法获取泛型的 class 对象,如
- 解决方案:通过反射 + 类型令牌 (typetoken) 可以获取泛型的具体类型,如 spring 的 resolvabletype。
二、java 并发编程(重中之重,高级开发核心分水岭,30%)
✅ 核心说明:并发编程是 java 高级开发面试的绝对核心,占比最高、区分度最大,大厂面试必问,从基础的线程、锁到进阶的 aqs、cas、线程池,全部是高频考点,必须吃透!
1. 创建线程的方式有哪些?哪种最好?【基础必问】
答案要点(标准答案,4 种方式,按推荐度排序)
- 继承 thread 类:重写 run () 方法,缺点:java 单继承,无法继承其他类,耦合度高;
- 实现 runnable 接口:实现 run () 方法,优点:支持多继承,解耦,推荐;
- 实现 callable 接口 + futuretask:实现 call () 方法,支持返回值 + 抛出异常,这是和 runnable 的核心区别,适合需要获取异步执行结果的场景;
- 线程池创建(threadpoolexecutor):生产环境最优选择 ✅,优点:① 复用线程,避免频繁创建销毁线程的开销;② 控制并发数,避免 oom;③ 提供丰富的任务管理(核心线程、最大线程、队列、拒绝策略);
- 面试结论:优先使用线程池创建线程,避免手动创建线程;callable 适合有返回值的场景,runnable 适合无返回值的场景。
2. volatile 关键字的作用?原理?能保证原子性吗?【必考・高频】
答案要点(面试满分版,分点必背)
✔ volatile 核心三大作用(java 并发的基石)
- 保证可见性:当一个线程修改了 volatile 修饰的变量,其他线程能立即看到修改后的值,解决了线程间的内存可见性问题;
- 禁止指令重排序:编译器和 cpu 不会对 volatile 修饰的变量进行指令重排序,保证代码的执行顺序和编写顺序一致,解决了单例模式 dcl 的指令重排序问题;
- 不保证原子性:这是面试核心坑点,必须重点强调!
✔ volatile 实现原理
底层通过内存屏障 (memory barrier) 实现:
- 写屏障:对 volatile 变量写操作后,会插入写屏障,强制将变量的修改刷新到主内存,其他线程可见;
- 读屏障:对 volatile 变量读操作前,会插入读屏障,强制从主内存读取变量的最新值,而非线程工作内存的缓存值。
✔ 为什么 volatile 不能保证原子性?
原子性是指「一个操作要么全部执行成功,要么全部失败」,volatile 只能保证可见性和有序性,无法保证复合操作的原子性。
- 例子:
i++是复合操作(读 i→加 1→写 i),即使 i 被 volatile 修饰,多线程下依然会出现线程安全问题,最终结果小于预期值; - 解决方案:保证原子性的方式 →
synchronized/reentrantlock/atomicinteger(cas 原子类)。
✔ volatile vs synchronized 核心区别【必考】
- 作用范围:volatile 修饰变量;synchronized 修饰方法 / 代码块;
- 保证特性:volatile 保证可见性 + 有序性,不保证原子性;synchronized 保证可见性 + 有序性 + 原子性;
- 锁机制:volatile 是无锁,无性能开销;synchronized 是有锁,有性能开销(jdk8 已优化);
- 适用场景:volatile 适合单写多读的场景(如状态标记位);synchronized 适合多写多读的场景。
3. cas 是什么?原理?优点缺点?aba 问题及解决方案?【必考・高薪考点】
答案要点(cas 是并发编程的核心,必须吃透)
✔ cas 定义
cas = compare and swap(比较并交换),是一种无锁的原子操作,是 java 并发包(java.util.concurrent.atomic)的底层实现原理,核心思想是「无锁并发,乐观锁思想」。
✔ cas 核心原理
cas 有 3 个核心参数:内存地址v、旧的预期值a、新值b。
- 执行逻辑:线程从内存地址 v 读取值 a,修改为新值 b,在写入内存前,再次判断内存地址 v 的值是否还是 a;如果是,则写入 b,操作成功;如果不是(被其他线程修改),则放弃写入,自旋重试。
- 底层支持:cas 是 cpu 的原生指令(cmpxchg),由硬件保证原子性,性能远超锁机制。
✔ cas 的优点 & 缺点
✅ 优点:无锁机制,无需加锁释放锁,没有线程上下文切换的开销,并发量低时性能极高;❌ 缺点:
- 自旋重试开销:并发量高时,大量线程自旋重试,会占用 cpu 资源,导致性能下降;
- 只能保证单个变量的原子性:无法保证复合操作的原子性;
- aba 问题:cas 的核心缺陷,面试必问!
✔ aba 问题 & 解决方案【核心】
- aba 问题定义:线程 1 读取内存值为 a,准备修改为 b;此时线程 2 将内存值从 a 修改为 c,再修改回 a;线程 1 再次判断时,发现内存值还是 a,误以为未被修改,执行 cas 操作成功,这就是 aba 问题。
- aba 问题的危害:在简单场景下无影响,但在链表、栈等有状态的数据结构中,会导致数据结构的逻辑错误(如链表成环)。
- 解决方案:原子引用 + 版本号(时间戳) → java 提供
atomicstampedreference和atomicmarkablereference,在 cas 时不仅比较值,还比较版本号 / 标记位,只有值和版本号都一致时,才执行 cas 操作,彻底解决 aba 问题。
4. threadlocal 是什么?作用?内存泄漏问题及解决方案?【必考・高频坑点】
答案要点(面试满分版,分点必背,坑点必答)
✔ threadlocal 核心定义 & 作用
threadlocal = 线程本地变量,核心作用是:为每个线程创建一个独立的变量副本,线程之间的变量互不干扰,彻底解决多线程下的变量共享问题。
- 核心思想:空间换时间,用内存的开销换取线程安全,无锁机制,性能极高;
- 典型应用场景:① spring 的事务管理器(绑定当前线程的数据库连接);② 日志跟踪的 traceid;③ 日期格式化工具类(simpledateformat 线程不安全,用 threadlocal 封装)。
✔ threadlocal 底层原理
- thread 类中有一个
threadlocalmap成员变量,threadlocalmap 的 key 是弱引用的 threadlocal 对象,value 是线程的变量副本; - 每个线程的 threadlocalmap 是独立的,线程只能访问自己的 threadlocalmap,从而实现线程隔离。
✔ threadlocal 内存泄漏问题【核心坑点,面试必问】
- 内存泄漏的原因:
- ✔ threadlocalmap 的 key 是弱引用,当 threadlocal 对象被 gc 回收后,key 变为 null,而 value 是强引用,依然指向变量副本;
- ✔ 如果线程一直存活(如线程池的核心线程),value 永远不会被 gc 回收,导致内存泄漏。
- 解决方案(生产必做,背会):
- ✅ 核心方案:使用完 threadlocal 后,必须手动调用
remove()方法,删除当前线程的变量副本; - ✅ 规范写法:
try { ... } finally { threadlocal.remove(); },保证无论是否异常,都会执行 remove; - ✅ 补充:threadlocalmap 本身有过期清理机制,会在 get/set 时清理 key 为 null 的 entry,但不能依赖,手动 remove 是最优解。
- ✅ 核心方案:使用完 threadlocal 后,必须手动调用
5. 线程池的核心参数?拒绝策略?如何合理配置线程池?【必考・实战核心】
✅ 核心:线程池是生产环境的标配,面试必问核心参数 + 拒绝策略 + 配置方案,体现你的实战能力,这是加分项!
✔ 一、threadpoolexecutor 7 个核心参数(必背,按顺序说)
public threadpoolexecutor(int corepoolsize, // 核心线程数
int maximumpoolsize, // 最大线程数
long keepalivetime, // 空闲线程存活时间
timeunit unit, // 时间单位
blockingqueue<runnable> workqueue, // 任务队列
threadfactory threadfactory, // 线程工厂
rejectedexecutionhandler handler) // 拒绝策略- 核心线程数 (corepoolsize):线程池的常驻线程数,核心线程不会被回收,即使空闲;
- 最大线程数 (maximumpoolsize):线程池能创建的最大线程数,核心线程 + 非核心线程的总数上限;
- 空闲线程存活时间 (keepalivetime):非核心线程空闲超过该时间,会被回收,节省资源;
- 任务队列 (workqueue):存放等待执行的任务,核心线程满了后,任务会进入队列排队;
- 拒绝策略 (handler):当线程池满(线程数 = 最大线程数,队列已满),新任务会触发拒绝策略。
✔ 二、4 种默认拒绝策略(必背,生产常用)
- abortpolicy(默认):直接抛出
rejectedexecutionexception异常,拒绝任务,生产中不推荐,会导致业务报错; - callerrunspolicy:由提交任务的主线程执行该任务,不会抛出异常,能有效缓冲流量,生产推荐 ✅;
- discardpolicy:直接丢弃任务,不抛出异常,无任何提示,慎用;
- discardoldestpolicy:丢弃队列中最老的任务,将新任务加入队列,慎用。
✔ 三、线程池的任务执行流程(必背)
- 当任务提交时,若核心线程数未满,创建核心线程执行任务;
- 核心线程满了,任务进入任务队列排队;
- 任务队列满了,创建非核心线程执行任务;
- 非核心线程数达到最大线程数,触发拒绝策略。
✔ 四、如何合理配置线程池参数?【实战核心,面试加分】
✅ 核心原则:根据任务类型来配置,任务分为「cpu 密集型」和「io 密集型」,两者配置完全不同
- cpu 密集型任务(如计算、排序、加密):任务消耗 cpu 资源,线程数过多会导致 cpu 上下文切换频繁,性能下降;
- 配置:
核心线程数 = cpu核心数 + 1(最优值); - 队列:使用无界队列(如 linkedblockingqueue),避免拒绝任务。
- 配置:
- io 密集型任务(如数据库查询、redis 调用、网络请求):任务大部分时间在等待 io,cpu 空闲,线程数可以多配置;
- 配置:
核心线程数 = cpu核心数 * 2或cpu核心数 / (1 - 阻塞系数)(阻塞系数 0.8~0.9); - 队列:使用有界队列(如 arrayblockingqueue),避免队列无限扩容导致 oom。
- 配置:
- 生产规范:禁止使用 executors 创建线程池(如 newfixedthreadpool、newcachedthreadpool),会导致 oom;必须手动创建 threadpoolexecutor,指定核心参数和拒绝策略。
6. aqs 是什么?核心原理?有哪些基于 aqs 实现的类?【高薪必考・源码级】
答案要点(精简版,面试够用,体现原理功底)
- aqs 定义:aqs = abstractqueuedsynchronizer,是 java 并发包的核心基类,是一个抽象的同步队列框架,所有的锁和同步工具类都是基于 aqs 实现的。
- aqs 核心原理:
- ✔ 核心思想:用一个 volatile 的 int 变量表示同步状态(state),通过 cas 修改状态,保证原子性;
- ✔ 等待队列:维护一个双向链表的同步队列,所有获取锁失败的线程会加入队列,阻塞等待;
- ✔ 独占 / 共享模式:aqs 支持两种模式,① 独占模式(一把锁只能被一个线程持有,如 reentrantlock);② 共享模式(一把锁能被多个线程持有,如 countdownlatch、semaphore)。
- 基于 aqs 实现的类(必背):
reentrantlock、countdownlatch、semaphore、cyclicbarrier、reentrantreadwritelock。
- ✅ 面试加分:aqs 是 java 并发的基石,掌握 aqs 的原理,就能理解所有锁和同步工具的实现逻辑。
7. 公平锁和非公平锁的区别?reentrantlock 是公平还是非公平?【高频】
答案要点
- 公平锁:线程获取锁的顺序,和线程排队的顺序一致,先到先得,不会出现插队现象;
- 优点:公平,无饥饿问题;缺点:性能低,因为需要维护队列的顺序。
- 非公平锁:线程获取锁时,先尝试插队(直接 cas 获取锁),插队失败后,再加入队列排队;
- 优点:性能高,因为减少了队列的调度开销;缺点:可能出现线程饥饿(某些线程一直拿不到锁)。
- reentrantlock:默认是非公平锁,构造方法传入
true可以指定为公平锁:new reentrantlock(true); - synchronized:底层是非公平锁,且无法改为公平锁。
三、jvm 虚拟机(高薪分水岭,高级开发必考,调优是核心,20%)
✅ 核心说明:jvm 是 java 高级开发的高薪门槛,初级开发问基础,中高级开发问内存模型 + 垃圾回收 + 调优 + 内存泄漏,大厂面试必问,能答出 jvm 调优的人,薪资至少上浮 20%!
1. jvm 运行时数据区(内存模型)划分?各区域的作用?【必考・基础】
答案要点(jdk8 版本,必背,分点清晰,面试满分)jvm 运行时数据区分为 线程私有区域 和 线程共享区域,jdk8 移除了永久代,用元空间 (metaspace) 替代,这是核心考点!
✔ 一、线程私有区域(每个线程独立创建,互不干扰)
- 程序计数器 (pc register):存储当前线程执行的字节码指令地址,线程切换时恢复执行位置,无 oom,是 jvm 中唯一不会内存溢出的区域;
- 虚拟机栈 (vm stack):存储方法的调用栈帧(局部变量表、操作数栈、返回地址等),方法调用入栈,方法执行完成出栈;
- 异常:栈深度过大→
stackoverflowerror;栈内存不足→outofmemoryerror(oom);
- 异常:栈深度过大→
- 本地方法栈 (native method stack):和虚拟机栈类似,只是为native 本地方法服务,同样会抛出栈溢出和 oom 异常。
✔ 二、线程共享区域(所有线程共用,是 gc 的核心区域,面试重点)
- 堆 (heap):jvm 中最大的内存区域,存储所有对象实例和数组,是垃圾回收(gc)的主要战场,也是 oom 的高发区;
- 堆的划分:新生代(eden 区 + survivor 区 s0/s1) + 老年代,新生代存放年轻对象,老年代存放存活时间长的对象;
- 核心:堆内存的大小可以通过
-xms(初始堆内存)和-xmx(最大堆内存)配置,生产中建议两者设置为相同值,避免频繁扩容。
- 元空间 (metaspace,jdk8+):替代 jdk7 的永久代,存储类的元数据、常量池、方法区信息,元空间的内存是直接使用物理内存,默认无上限,可通过
-xx:metaspacesize和-xx:maxmetaspacesize配置;- 异常:元空间内存不足→
outofmemoryerror: metaspace。
- 异常:元空间内存不足→
2. 什么是 gc?gc 的回收对象?判断对象存活的算法有哪些?【必考】
答案要点
- gc 定义:gc = 垃圾回收 (garbage collection),是 jvm 的自动内存管理机制,自动回收堆中不再使用的对象,释放内存,无需程序员手动管理,是 java 的核心优势之一。
- gc 的回收对象:堆内存中的对象实例,虚拟机栈、程序计数器等线程私有区域不会被 gc 回收;
- 判断对象存活的两大核心算法:
- ✔ 引用计数法:给对象添加引用计数器,有引用时 + 1,引用失效时 - 1,计数器为 0 则对象死亡;
- 缺点:无法解决循环引用问题(如 a 引用 b,b 引用 a),导致对象无法被回收,java 不使用该算法;
- ✔ 可达性分析算法(java 使用):以「gc roots」为根节点,向下遍历对象的引用链,若对象到 gc roots 没有任何引用链,则对象被判定为死亡,可被 gc 回收;
- gc roots 包括:虚拟机栈中的局部变量、方法区中的静态变量、本地方法栈中的 native 对象等。
- ✔ 引用计数法:给对象添加引用计数器,有引用时 + 1,引用失效时 - 1,计数器为 0 则对象死亡;
3. 垃圾收集器有哪些?cms 和 g1 的区别?生产环境如何选择?【必考・调优核心】
✅ 核心:垃圾收集器是 jvm 调优的核心,面试必问 cms 和 g1,这是目前生产环境的主流收集器,jdk9 后 g1 成为默认收集器。
✔ 一、主流垃圾收集器(按优先级排序)
- serialgc:串行收集器,单线程回收,新生代用复制算法,老年代用标记 - 整理算法,适合单线程、小内存、客户端程序,生产环境几乎不用;
- parallelgc(并行收集器):jdk8 默认收集器,新生代并行回收,老年代串行回收,追求吞吐量,适合后台计算、批处理任务,无停顿要求的场景;
- cms(concurrent mark sweep):老年代收集器,并发收集、低停顿,基于「标记 - 清除」算法,核心是追求最短的 gc 停顿时间,适合高并发、响应时间敏感的业务(如电商、金融);
- g1(garbage first):jdk9 默认收集器,新生代 + 老年代通用收集器,基于「标记 - 整理」算法,核心是兼顾吞吐量和低停顿,适合大内存、高并发的生产环境,是目前的最优解 ✅。
✔ 二、cms vs g1 核心区别(面试必答,满分答案)
- 回收范围:cms 只回收老年代,新生代需要配合 parallelgc;g1 回收整个堆(新生代 + 老年代),无需搭配其他收集器;
- 内存划分:cms 的堆是连续的,新生代是 eden+s0+s1,老年代是连续区域;g1 将堆划分为多个大小相等的 region,新生代和老年代交错分布,灵活度更高;
- 回收算法:cms 是「标记 - 清除」,会产生内存碎片,导致频繁 full gc;g1 是「标记 - 整理」,无内存碎片,内存利用率高;
- 停顿控制:cms 只能控制老年代的停顿,无法精准控制整体停顿;g1 支持可预测的停顿时间,可以设置
-xx:maxgcpausemillis,保证 gc 停顿不超过指定时间; - 适用场景:cms 适合小内存(<=8g)、低停顿的场景;g1 适合大内存(>=8g)、兼顾吞吐量和低停顿的场景,生产环境优先选择 g1 ✅。
4. 什么是内存泄漏和内存溢出?常见的内存泄漏场景?如何排查?【必考・实战核心】
✔ 一、内存泄漏 vs 内存溢出(核心区别,面试必问)
- 内存泄漏(memory leak):对象不再被使用,但依然被其他对象持有引用,无法被 gc 回收,导致堆内存逐渐耗尽,是内存溢出的根源;
- 特点:内存泄漏是渐进式的,内存占用会慢慢升高,最终导致 oom;
- 内存溢出(oom):jvm 的堆内存 / 元空间不足,无法为新对象分配内存,抛出
outofmemoryerror异常,程序崩溃;- 核心关系:内存泄漏 → 内存溢出,内存泄漏是因,内存溢出是果。
✔ 二、java 中常见的内存泄漏场景(必背,生产高频)
- 静态集合类:
static list/map持有对象的强引用,对象使用后不删除,导致永远无法被 gc 回收; - 单例模式:单例持有外部对象的强引用,外部对象生命周期结束后,依然被单例持有,无法回收;
- 未关闭的资源:数据库连接、redis 连接、文件流、socket 连接等,使用后未调用
close()关闭,资源句柄被持有,导致内存泄漏; - threadlocal 内存泄漏:使用后未调用
remove(),导致 value 无法被 gc 回收(前文已讲); - 匿名内部类 / 内部类:内部类持有外部类的强引用,外部类生命周期结束后,被内部类持有无法回收。
✔ 三、内存泄漏 / 溢出的排查方案(生产实战,面试加分,必背)
- 开启 jvm 参数,打印 gc 日志和堆转储文件:
-xx:+heapdumponoutofmemoryerror -xx:heapdumppath=./dump.hprof; - 当发生 oom 时,会自动生成 dump 文件,使用工具分析:mat(memory analyzer tool) 或 jprofiler,分析大对象、内存泄漏的引用链;
- 用 jdk 自带命令排查:
jps(查看进程 id)→jstat(查看 gc 情况)→jmap(导出堆快照)→jhat(分析堆快照)。
5. jvm 性能调优的核心思路?常用的调优参数?【必考・高薪考点】
✔ 一、jvm 调优的核心思路(必背,分步骤,体现实战能力)
✅ 核心原则:先业务优化,后 jvm 调优,调优不是目的,目的是解决性能问题(如频繁 gc、oom、响应慢),调优的核心是「减少 full gc 的次数,降低 gc 的停顿时间」
- 第一步:定位问题:通过 gc 日志、监控工具(prometheus+grafana、skywalking),确定问题类型(内存泄漏、频繁 gc、full gc 过多、停顿时间过长);
- 第二步:基础调优:合理配置堆内存(-xms=-xmx)、新生代比例(-xx:newratio)、元空间大小,避免内存不足导致的 oom;
- 第三步:收集器调优:根据业务场景选择收集器(响应优先选 g1,吞吐量优先选 parallelgc),配置收集器的参数;
- 第四步:代码优化:解决内存泄漏问题,减少大对象创建,避免频繁创建临时对象,使用池化技术(线程池、连接池);
- 第五步:验证效果:观察 gc 日志,看 full gc 次数是否减少、停顿时间是否降低、程序响应是否提升。
✔ 二、常用的 jvm 调优参数(必背,生产高频)
- 堆内存配置:
-xms2g -xmx2g(初始堆 2g,最大堆 2g,推荐相等); - 新生代配置:
-xx:newratio=2(新生代:老年代 = 1:2)、-xx:survivorratio=8(eden:s0:s1=8:1:1); - 收集器配置:
-xx:+useg1gc(使用 g1 收集器)、-xx:maxgcpausemillis=200(g1 最大停顿时间 200ms); - gc 日志配置:
-xx:+printgcdetails -xx:+printgctimestamps -xloggc:./gc.log(打印 gc 日志); - 元空间配置:
-xx:metaspacesize=256m -xx:maxmetaspacesize=512m。
四、java 分布式 & 框架深度(结合之前 spring/mysql/redis,15%)
1. spring 解决循环依赖的核心原理?【必考,和之前 spring 面试题衔接】
答案要点(精简版,面试够用)
- 循环依赖:a 依赖 b,b 依赖 a,spring 容器启动时会出现死循环;
- spring只解决单例 bean 的属性注入循环依赖,原型 bean / 构造器注入的循环依赖无法解决;
- 核心方案:三级缓存机制
- 一级缓存:
singletonobjects→ 存放完全初始化的 bean; - 二级缓存:
earlysingletonobjects→ 存放实例化完成、未初始化的早期 bean; - 三级缓存:
singletonfactories→ 存放 bean 的工厂对象,解决 aop 代理的循环依赖;
- 一级缓存:
- 核心逻辑:通过三级缓存,提前暴露 bean 的早期对象,让依赖的 bean 能拿到引用,避免死循环。
2. spring 事务失效的常见场景?【必考】
答案要点(高频 8 种,前文已详细讲,精简版)
- 注解加在非 public 方法上;
- 同类中无事务方法调用有事务方法(内部调用,不走代理);
- 事务方法手动捕获异常不抛出;
- 抛出非 runtimeexception 异常(默认只回滚运行时异常);
- 传播机制配置错误(如 not_supported);
- 目标对象不是 spring 容器的 bean;
- 数据库不支持事务(如 myisam);
- 配置了
readonly=true的只读事务。
3. 分布式事务的解决方案?优缺点及适用场景?【必考・架构级】
答案要点(按生产使用频率排序,必背)
- 可靠消息最终一致性(rocketmq/kafka 事务消息):核心是「本地事务 + 消息队列」,最终一致性,无侵入,性能高,生产首选 ✅,适合电商、支付等大部分业务;
- tcc 模式(补偿事务):try-confirm-cancel,手动编写正向和反向补偿逻辑,强一致性,侵入性高,适合金融等高一致性场景;
- seata at 模式:无侵入,基于数据库本地事务 + undo log,性能高,适合微服务架构,生产常用 ✅;
- saga 模式:长事务拆分,按顺序执行,失败后反向补偿,适合超长事务(如订单履约);
- 2pc 模式:强一致性,性能差,适合低并发、高一致性场景(如金融对账)。
4. 缓存穿透、缓存击穿、缓存雪崩的区别及解决方案?【必考,衔接 redis 面试题】
答案要点(精简版,必背)
- 缓存穿透:请求不存在的 key,缓存和 db 都查不到 → 解决方案:缓存空值、布隆过滤器;
- 缓存击穿:热点 key 过期,大量请求打穿到 db → 解决方案:热点 key 永不过期、分布式锁、缓存预热;
- 缓存雪崩:大量 key 同时过期 / redis 宕机 → 解决方案:随机过期时间、redis 集群、熔断降级、本地缓存兜底。
五、项目实战 & 架构设计(压轴,体现综合能力,10%)
1. 高并发系统的优化方案?【必考・架构师必问】
答案要点(分维度,必背,万能答案)
- 应用层:接口限流(redis+lua)、异步处理(@async/completablefuture)、池化技术(线程池 / 连接池)、熔断降级(sentinel/hystrix);
- 缓存层:多级缓存(本地 caffeine+redis)、缓存预热、缓存更新策略(先更 db 再删缓存)、防止缓存问题;
- 数据库层:读写分离、分库分表、索引优化、sql 优化、批量操作、避免大事务;
- 架构层:微服务拆分、集群部署、负载均衡(nginx/ribbon)、高可用(主从 / 哨兵 / 集群)。
2. 如何保证接口的幂等性?【必考・实战核心】
答案要点(按优先级排序,必背)
- 数据库唯一索引:最基础的方案,插入数据时用唯一索引,重复插入报错,适合新增场景;
- redis 分布式锁:请求前加锁,处理完成释放锁,适合更新 / 删除场景;
- 幂等令牌:请求前获取令牌,请求时携带令牌,处理完成后销毁令牌,适合接口调用场景;
- 乐观锁(版本号):更新时判断版本号,适合更新场景;
- 业务状态机:通过业务状态判断是否已处理,适合订单 / 支付等有状态的业务。
面试加分小技巧(高级开发必看)
- 回答逻辑:所有问题都分点作答,先讲定义,再讲原理,最后讲解决方案 / 适用场景,面试官喜欢思路清晰的候选人;
- 核心重点:java 高级开发的面试核心是 「并发编程 + jvm + 分布式」,这三个模块吃透,薪资至少拔高一个档次;
- 原理结合实战:回答问题时,尽量结合项目经验,比如「我在项目中用线程池解决了 xx 问题」「我通过 jvm 调优把 full gc 次数从 10 次 / 天降到 0 次」,体现你的实战能力;
- 避坑原则:不会的问题不要瞎答,坦诚说「这个点我还在学习中」,比答错强得多。
到此这篇关于java高级开发高频面试题完整版的文章就介绍到这了,更多相关java高级开发高频面试题内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论