爆肝整理 Java 面试八股文,大厂高频考点全在这了
最近春招了,我把这几个月刷过的面经、被问过的问题、踩过的坑全部整理了一遍。
不敢说全,但这些考点覆盖了我面过的字节、腾讯、美团、京东、虾皮的 80% 以上的问题。
收藏备用,别等到面试前一天再找。
Java 基础
== 和 equals 的区别,为什么重写 equals 必须重写 hashCode?
== 比较的是引用地址,equals 默认也是比较地址,但可以重写为比较内容。重写 equals 必须重写 hashCode 是因为 Java 规定相等的对象必须有相同的哈希值,如果只重写 equals 不重写 hashCode,两个内容相同的对象放进 HashMap 会被当成不同的 key,逻辑错误。
String、StringBuilder、StringBuffer 的区别?
String 不可变,每次拼接都创建新对象,频繁拼接性能差。StringBuilder 可变,非线程安全,单线程拼接用这个。StringBuffer 可变,线程安全,方法加了 synchronized,多线程场景用这个,但性能比 StringBuilder 差。
final、finally、finalize 分别是什么?
final 修饰类不可继承、修饰方法不可重写、修饰变量不可修改。finally 是 try-catch 的最终执行块,无论是否异常都会执行,常用于资源释放。finalize 是 Object 的方法,GC 回收对象前调用,已经被废弃,不要用。
Java 的异常体系,Exception 和 Error 的区别,受检异常和非受检异常?
Throwable 是根类,分 Error 和 Exception。Error 是 JVM 层面的严重问题,比如 OutOfMemoryError、StackOverflowError,程序无法处理。Exception 是程序可以处理的异常,分受检异常(编译器强制处理,比如 IOException)和非受检异常(运行时异常,比如 NullPointerException、ArrayIndexOutOfBoundsException)。
集合框架
ArrayList 和 LinkedList 的区别,什么时候用哪个?
ArrayList 底层数组,随机访问 O(1),中间插入删除 O(n),扩容时复制数组。LinkedList 底层双向链表,随机访问 O(n),头尾插入删除 O(1),但每个节点有额外的指针开销,缓存不友好。大多数场景用 ArrayList,只有频繁在中间插入删除且不需要随机访问时才考虑 LinkedList。
HashMap 的底层实现,Java 8 做了哪些优化?
底层是数组加链表,哈希冲突用链地址法解决。Java 8 引入红黑树,链表长度超过 8 且数组长度超过 64 时转红黑树,查找从 O(n) 降到 O(log n)。扩容时 rehash 优化,利用容量翻倍的特性,不需要重新计算哈希,只看新增的那一位是 0 还是 1。插入改为尾插法,避免多线程扩容时的环形链表问题。
ConcurrentHashMap 怎么实现线程安全的,Java 7 和 Java 8 的区别?
Java 7 用分段锁,把 Map 分成若干 Segment,每个 Segment 有独立的锁,最多支持 16 个线程并发写。Java 8 改用 CAS + synchronized,锁粒度细化到单个桶,并发度更高,扩容时多线程协作迁移数据。
HashMap 为什么线程不安全,多线程下会有什么问题?
Java 7 多线程扩容时头插法可能形成环形链表,导致 get 操作死循环。Java 8 改了插入方式,不会死循环,但多线程同时 put 可能导致数据覆盖丢失,size 统计也不准确。多线程场景用 ConcurrentHashMap。
更多全面的java大厂面试题和必备八股文都收录在专栏里面了:
https://www.nowcoder.com/creation/manager/columnDetail/0n9XOd
JVM
JVM 内存结构,各个区域存什么?
堆:存放对象实例,GC 的主要区域,分新生代(Eden + 两个 Survivor)和老年代。方法区(元空间):存放类信息、常量、静态变量、JIT 编译后的代码。虚拟机栈:每个线程私有,存放栈帧,每个方法调用对应一个栈帧,存局部变量表、操作数栈、动态链接、返回地址。本地方法栈:native 方法的栈。程序计数器:记录当前线程执行的字节码行号,线程私有,唯一不会 OOM 的区域。
垃圾回收算法有哪些,G1 收集器的工作原理?
基础算法:标记清除(有碎片)、标记整理(无碎片但有移动开销)、复制算法(无碎片但内存利用率低)。现代 JVM 分代收集,新生代用复制算法,老年代用标记整理。G1 把堆分成大量 Region,优先回收垃圾最多的 Region,支持设置停顿时间目标,是 JDK 9 之后的默认收集器。
什么是 STW,怎么减少 STW 时间?
STW(Stop The World)是 GC 时暂停所有用户线程的阶段,保证 GC 过程中对象引用关系不变。减少 STW 的方式:用并发 GC(G1、ZGC),大部分标记工作和用户线程并发执行,只有少数阶段需要 STW。ZGC 把 STW 时间控制在毫秒级以内。
类加载机制,双亲委派模型是什么,什么时候需要打破?
类加载分加载、验证、准备、解析、初始化五个阶段。双亲委派:类加载器收到请求先委派给父类加载器,父类找不到才自己加载,保证核心类库唯一性。需要打破的场景:SPI 机制(JDBC 驱动)、Tomcat 的 Web 应用隔离、OSGi 模块化、热部署。
OOM 有哪些类型,分别怎么排查?
堆 OOM(Java heap space):对象太多或者内存泄漏,用 MAT 分析堆转储文件找大对象。元空间 OOM(Metaspace):类加载太多,通常是动态生成类或者类加载器泄漏。栈 OOM(StackOverflowError):递归太深,检查递归是否有终止条件。直接内存 OOM:NIO 使用直接内存过多,检查 ByteBuffer 的使用。
并发编程
synchronized 的锁升级过程,偏向锁、轻量级锁、重量级锁?
无锁 → 偏向锁(只有一个线程访问,在对象头记录线程 ID,下次同一线程进入不需要 CAS)→ 轻量级锁(有竞争时升级,用 CAS 操作,失败则自旋等待)→ 重量级锁(自旋超过阈值,升级为操作系统互斥量,线程阻塞)。锁只能升级不能降级(偏向锁可以撤销)。
volatile 的作用,能保证原子性吗?
两个作用:保证可见性(写操作立即刷新到主内存,读操作从主内存读取)、禁止指令重排(通过内存屏障)。不能保证原子性,i++ 这种复合操作不是原子的,需要用 AtomicInteger 或者 synchronized。
ThreadLocal 的原理,内存泄漏怎么产生的?
每个线程有一个 ThreadLocalMap,key 是 ThreadLocal 对象的弱引用,value 是存储的值。内存泄漏:ThreadLocal 对象被 GC 回收后,key 变成 null,但 value 还在,如果线程是线程池里的长期存活线程,value 永远不会被回收。解决方法是用完后调用 remove()。
线程池的核心参数和任务提交流程,拒绝策略怎么选?
核心参数:核心线程数、最大线程数、非核心线程存活时间、工作队列、拒绝策略。提交流程:线程数小于核心线程数直接创建线程 → 放入队列 → 队列满了且线程数小于最大线程数创建非核心线程 → 触发拒绝策略。四种拒绝策略:AbortPolicy 抛异常(默认)、CallerRunsPolicy 调用方执行(反压)、DiscardPolicy 丢弃、DiscardOldestPolicy 丢弃最老的任务。
ReentrantLock 和 synchronized 的区别?
ReentrantLock 支持超时获取锁、可中断等待、公平锁、多个条件变量,功能更丰富。synchronized 使用简单,JVM 自动释放,有锁升级优化。两者都是可重入锁。简单场景用 synchronized,需要高级功能用 ReentrantLock,记得在 finally 里释放。
MySQL
索引的底层结构,为什么用 B+ 树?
B+ 树所有数据在叶子节点,叶子节点双向链表连接,非叶子节点只存键值。比 B 树好:非叶子节点不存数据,同样大小能存更多键值,树更矮,IO 次数更少;叶子节点链表天然支持范围查询。比哈希索引好:支持范围查询、排序、前缀匹配,哈希索引只支持等值查询。
事务的四个特性和四个隔离级别,可重复读怎么实现的?
ACID:原子性(要么全成功要么全失败)、一致性(事务前后数据合法)、隔离性(事务间互不干扰)、持久性(提交后永久保存)。四个隔离级别:读未提交、读已提交、可重复读(MySQL 默认)、串行化。可重复读通过 MVCC 实现,事务开始时创建 ReadView,整个事务期间读同一个版本的数据。
MVCC 的原理,ReadView 是什么时候创建的?
每行数据有隐藏的事务 ID 和回滚指针,旧版本数据通过回滚指针链接成版本链存在 undo log。ReadView 记录创建时刻的活跃事务列表,读数据时根据 ReadView 判断哪个版本可见。读已提交每次 SELECT 创建新 ReadView,可重复读只在第一次 SELECT 时创建,这是两者的本质区别。
索引失效的场景有哪些?
对索引列做函数运算或类型转换、LIKE 以通配符开头(%abc)、联合索引不满足最左前缀、OR 连接的条件有一个没有索引、使用 != 或 <> 、数据量少时优化器认为全表扫描更快、字符串不加引号导致隐式类型转换。
explain 的 type 字段,怎么判断索引是否生效?
从好到差:system/const(主键或唯一索引等值查询)→ eq_ref(联表唯一匹配)→ ref(非唯一索引)→ range(索引范围扫描)→ index(全索引扫描)→ ALL(全表扫描)。key 字段显示实际使用的索引,NULL 表示没用索引。Extra 里 Using index 是覆盖索引,Using filesort 是额外排序,Using temporary 是临时表,后两个需要优化。
Redis
Redis 的数据结构有哪些,各自适合什么场景?
String:缓存、计数器、分布式锁。Hash:存储对象,比如用户信息。List:消息队列、最新列表。Set:去重、标签、共同好友。ZSet(有序集合):排行榜、带权重的队列。BitMap:用户签到、在线状态。HyperLogLog:UV 统计(近似值,内存极小)。
Redis 的持久化方式,RDB 和 AOF 怎么选?
RDB 快照持久化,文件紧凑,恢复快,但两次快照之间的数据可能丢失,fork 子进程有开销。AOF 追加日志,数据更完整,最多丢 1 秒(everysec 策略),但文件大,恢复慢,需要定期 rewrite。生产环境通常两者都开,RDB 用于快速恢复,AOF 保证数据完整性。
缓存穿透、缓存击穿、缓存雪崩的区别和解决方案?
穿透:查询不存在的数据,每次都打到数据库。解决:布隆过滤器过滤非法 key,或者缓存空值。击穿:热点 key 过期瞬间大量请求打到数据库。解决:热点 key 不过期,或者互斥锁只让一个线程重建缓存。雪崩:大量 key 同时过期或者 Redis 宕机。解决:过期时间加随机偏移,Redis 高可用部署,限流熔断保护数据库。
Redis 分布式锁的实现,有哪些坑?
用 SET key value NX PX timeout 原子命令,value 用唯一标识,释放时用 Lua 脚本保证判断和删除的原子性。坑:锁过期但业务没执行完(用 watchdog 续期,Redisson 实现了)、Redis 主从切换导致锁丢失(用 Redlock 多节点方案,但有争议)。
Spring
Spring IOC 的原理,Bean 的生命周期?
IOC 是控制反转,把对象的创建和依赖管理交给容器。Bean 生命周期:实例化 → 属性注入 → Aware 接口回调 → BeanPostProcessor 前置处理 → 初始化(@PostConstruct → afterPropertiesSet → init-method)→ BeanPostProcessor 后置处理 → 使用 → 销毁(@PreDestroy → destroy → destroy-method)。
Spring AOP 的原理,JDK 动态代理和 CGLIB 的区别?
AOP 通过动态代理实现,在 Bean 初始化阶段创建代理对象。JDK 动态代理基于接口,被代理类必须实现接口,通过 InvocationHandler 拦截方法调用。CGLIB 基于继承,生成被代理类的子类,不需要接口,但 final 类和方法无法代理。Spring Boot 2.x 默认用 CGLIB。
Spring 的三级缓存,怎么解决循环依赖?
三级缓存:一级缓存存完整 Bean,二级缓存存早期 Bean(已实例化未初始化),三级缓存存 Bean 工厂(用于创建代理对象)。循环依赖解决:A 依赖 B,B 依赖 A,A 实例化后把自己的工厂放入三级缓存,注入 B 时 B 从三级缓存拿到 A 的引用完成初始化,A 再完成初始化。构造器注入的循环依赖无法解决。
@Transactional 失效的场景有哪些?
方法不是 public 的、同一个类内部方法调用(没有经过代理)、异常被 catch 吞掉没有抛出、抛出的是受检异常但没有配置 rollbackFor、数据库引擎不支持事务(MyISAM)、多线程场景下事务不传播到子线程。
消息队列
Kafka 为什么吞吐量高?
顺序写磁盘(追加写日志文件,接近内存速度)、零拷贝(sendfile 系统调用,数据从磁盘直接到网络不经过用户空间)、批量处理(生产者批量发送,消费者批量拉取)、页缓存(大量依赖 OS 页缓存,热数据不读磁盘)、分区并行(多分区并行读写)。
消息丢失和重复消费怎么处理?
消息丢失:生产者设置 acks=all,消费者手动提交 offset,Broker 设置多副本。重复消费:Kafka 保证至少一次投递,消费者需要实现幂等,用消息唯一 ID 做去重,处理前查 Redis 或数据库有没有处理记录。
分布式
分布式事务的解决方案?
2PC(两阶段提交):强一致,但协调者单点,性能差,适合少量参与方。TCC(Try-Confirm-Cancel):业务层面的分布式事务,性能好,但业务侵入性强。消息队列最终一致性:实现简单,性能好,但只能保证最终一致,适合允许短暂不一致的场景。Saga:长事务拆分成本地事务序列,每步有补偿操作,适合业务流程长的场景。
CAP 理论,实际系统怎么取舍?
C(一致性)、A(可用性)、P(分区容错性),三者不能同时满足,分布式系统 P 必须保证,所以是 CP 还是 AP 的选择。CP 系统(ZooKeeper):分区时停止服务保证一致,适合金融、库存等对数据准确性要求高的场景。AP 系统(Cassandra):分区时继续服务但可能返回旧数据,适合购物车、评论等允许短暂不一致的场景。
最后说一句
八股文是敲门砖,但面试官真正想看的是你能不能把这些知识点和实际项目结合起来。
背完这些,再想想你的项目里用到了哪些,遇到了什么问题,怎么解决的,这才是真正能拉开差距的地方。
祝大家都能拿到心仪的 offer。