
2026年Java后端面试的激烈程度不用我多说了。很多人一边刷LeetCode一边背八股文最后却挂在为什么上。所谓八股文其实是Java面试里那些结构化、高频重复、有标准答案的基础知识点从HashMap扩容到JVM垃圾回收再到Spring AOP代理。这篇文章我就把近一年面试里反复出现、我觉得最值得背的内容按模块整理成了一份速查级汇总每个问题都附带可以直接说出口的答案和容易踩的坑。1. 面试八股文到底在考什么1.1 为什么问这么多基础题很多人一提到八股文就反感觉得面试官在刁难人。但站在面试官的角度想项目经验可以包装业务代码可以背但基础题最难装。问一句HashMap在JDK 8里做了什么优化立刻就能看出你是背了面试题还是真跑过代码、看过源码。Java基础题考的从来不是记性而是三样东西一是对底层原理的理解深度二是把复杂问题讲清楚的能力三是知识体系是否成网。比如问你volatile如果只回答保证可见性那只是背了一个名词如果能继续说出JMM内存模型、指令重排、内存屏障、为什么不能保证原子性这才算真正掌握。1.2 怎么背才能不白背我的建议是分三层来准备第一层是知道是什么比如能说出AQS的全称是AbstractQueuedSynchronizer第二层是理解为什么比如为什么AQS要用双向队列、为什么状态用volatile修饰第三层是讲得出细节比如加锁失败后节点怎么入队、怎么唤醒后继节点。三层都过一遍才算把一道题吃透。这里有一个很笨但很有效的方法把每一道高频题当作一道小讲课假装自己在给同事讲原理用口语讲一遍卡壳的地方就是你没真正理解的地方。面试前如果能对着空气讲清楚十道题上考场就不会慌。2. Java核心基础高频题集合、异常、反射与泛型2.1 集合框架ArrayList和HashMap是基础也是坑集合这块十个面试官有九个会从HashMap开始问。先把最核心的几道题过一遍。问题1HashMap在JDK 7和JDK 8之间有什么变化答案要点JDK 8引入了红黑树。当链表长度超过阈值8且数组容量大于等于64时链表转为红黑树目的是把查询复杂度从O(n)降到O(log n)。新增节点从头插法变成了尾插法JDK 7的头插法在并发扩容时会形成环形链表导致死循环虽然现在大家都不推荐在多线程下用HashMap但作为历史知识点还是经常被问到。问题2HashMap的put流程是怎样的这个问题一定要能完整说出来先对key的hashCode做扰动计算也就是高16位和低16位异或然后通过(n - 1) hash来定位到数组下标如果下标位置为空直接放入Node不为空就判断key是否相同相同则替换value否则判断当前节点是不是树节点是树节点就走红黑树插入不是就遍历链表链表长度超过8且数组长度大于等于64就树化最后判断size是否超过threshold超过就扩容。这里容易被追问的是为什么要用异或扰动因为直接使用hashCode参与取模散列效果通常不好把高16位与低16位异或能让高位信息也参与进来从而降低哈希碰撞概率。另外一个高频追问是为什么HashMap容量是2的幂因为只有容量是2的幂时(n - 1) hash才等价于hash % n而且位运算比取模快得多。问题3ConcurrentHashMap的并发控制是怎么实现的JDK 7采用的是Segment分段锁把整个Map分成16个Segment每个Segment是一把可重入锁理论上支持16个线程并发写。JDK 8废除了分段锁改用CAS加synchronized首先在插入时如果对应桶为空就用CAS把节点放进桶里避免加锁如果桶不为空则对桶里的头节点加synchronized锁同时提供了LongAdder类似的计数思路来维护size。这个演进方向体现的其实是锁粒度越来越细的思路回答时如果能把这个趋势说出来会很加分。整个集合框架里ArrayList、LinkedList、HashSet、TreeMap也经常出现但核心始终是HashMap和ConcurrentHashMap。建议自己动手画一遍存储结构图把数组、链表、红黑树、扩容后的迁移过程画出来比干背文字管用。2.2 异常与反射常被追问的底层原理异常这块看似简单但问得细很容易露馅。几个必背题目问题1受检异常和非受检异常有什么区别受检异常checked exception必须显式捕获或往外抛编译器会强制检查典型的有IOException、SQLException非受检异常unchecked exception是RuntimeException及其子类比如NullPointerException、IllegalArgumentException编译器不检查。判断标准很简单继承自Exception但不继承自RuntimeException的是受检异常继承自RuntimeException的就是非受检异常。问题2Error和Exception有什么区别两者都继承自Throwable。Error一般是JVM层面的严重问题比如OutOfMemoryError、StackOverflowError程序通常无法恢复不应该去捕获Exception是程序运行层面的问题可以通过代码处理。面试官常追问OOM是Error还是Exception答案是Error。问题3try-with-resources的原理是什么JDK 7引入的语法糖使用实现了AutoCloseable接口的资源时会在try块结束后自动调用close方法。它解决的是传统try-finally里资源关闭代码冗长、且finally里close又抛异常会把原始异常吞掉的问题。反编译后会看到编译器自动生成了close调用和suppressed异常的记录逻辑。反射也是高频区域。最常问的是反射有什么用和反射为什么慢。反射可以在运行时获取类的完整结构、调用私有方法和私有字段Spring IOC、动态代理、注解处理都依赖反射。性能慢的原因比较复杂方法调用时需要做权限检查、参数解析很多反射调用没有办法做JIT热点优化还会触发类加载过程中的一些额外开销。日常开发里能用接口或者函数式编程替代反射的地方尽量别用反射。2.3 泛型面试里最容易答空的一块泛型在面试里被问得不如HashMap多但一旦问到很多人都会卡壳。问题1Java的泛型是怎么实现的Java的泛型是基于擦除机制实现的。编译时就能捕捉类型错误但对类型变量做了类型擦除编译过后Class文件中只能看到原始类型。也就是说List 和List 在运行时是同一个类型List。泛型信息只存在于编译阶段所以泛型类不能持有类型信息不能直接new T()。问题2? extends T和? super T有什么区别这其实是PECS原则Producer Extends, Consumer Super。? extends T表示某个T的子类型适合只读场景因为编译器不知道实际的元素类型是什么不能往里添加元素? super T表示某个T的父类型适合只写场景因为往里添加T及其子类型都是安全的但往外读取时只能读取Object类型。常见例子Collections.copy方法就同时用到了extends和super。问题3为什么不能创建泛型数组因为数组在运行时是协变的String[]可以赋值给Object[]如果泛型也能创建数组擦除后类型信息丢失堆污染问题很难被约束。举个例子new List [10]这个操作如果允许那么在类型擦除后变成new List[10]一个List 也能放进去数组在运行时的类型检查就会失效破坏类型安全。所以编译器直接拒绝这个操作。3. JVM与内存管理面试官最喜欢的深水区3.1 JVM内存区域划分一张图记住运行时数据区JVM题目几乎是高级开发和校招的必考项。首先要熟练掌握运行时数据区。堆Heap对象实例和数组分配的主要区域也是垃圾回收的主战场通过-Xms和-Xmx控制大小。虚拟机栈JVM Stack线程私有每个方法调用都会创建一个栈帧栈帧里存放局部变量表、操作数栈、动态链接、方法出口。栈深度不够时抛StackOverflowError。本地方法栈Native Method Stack为native方法服务线程私有。程序计数器Program Counter Register保存当前线程执行字节码的行号属于线程私有是唯一不会OOM的区域。方法区/元空间Method Area/Metaspace存放类信息、常量、静态变量。JDK 8用元空间替换永久代元空间使用的是本地内存。对象创建流程也要能讲清楚。先说类加载检查然后为对象分配内存分配方式有指针碰撞Bump the Pointer和空闲列表Free List两种具体取决于堆是否规整而堆是否规整又取决于垃圾回收器是否带压缩整理功能。接下来是并发安全问题有两种处理方式一种是对分配空间的动作做同步处理另一种是本地线程分配缓冲TLAB。最后是初始化零值、设置对象头、执行构造方法。3.2 垃圾回收算法与收集器把停顿时间聊明白垃圾回收是JVM的绝对重点。三个基础算法标记-清除先标记可回收对象再统一回收。缺点是产生大量内存碎片标记和清除效率都不高。标记-复制把内存分成两块只使用其中一块回收时把存活对象复制到另一块。适合新生代这种存活对象少的场景缺点是浪费一半空间。标记-整理标记完存活对象后把所有存活对象向一端移动然后清理边界外的内存。适合老年代没有碎片但移动对象的开销大。HotSpot默认采用分代收集新生代用复制算法老年代用标记-整理或标记-清除。面试常问的新生代划分是Eden区和两个Survivor区默认比例8:1:1每次Minor GC之后存活对象在Eden和两个Survivor之间不断复制年龄达到阈值进入老年代。收集器对比也是高频题。Serial是单线程适合客户端Parallel Scavenge关注吞吐量CMS是并发收集器目标是低停顿但会产生内存碎片G1是JDK 9以后的默认收集器把堆划分成多个Region可以指定最大停顿时间用回收价值最高的Region优先回收ZGC的目标是把停顿时间控制在10ms以内使用的是着色指针等新技术。如果面试官问你了解ZGC吗能说出基于区域、不分代、可达性分析用染色指针标记、支持TB级别堆、STW时间极短就算过关。3.3 类加载机制与双亲委派为什么不能重写String类加载过程分五步加载、验证、准备、解析、初始化。加载是找到Class文件字节流并生成Class对象验证是校验字节码安全性准备是为静态变量分配内存并设置零值解析是把常量池里的符号引用替换为直接引用初始化是执行类构造器 方法。双亲委派模型是另一个必问点。启动类加载器Bootstrap负责加载JDK核心类扩展类加载器Extension和应用程序类加载器Application逐级向下。当一个类需要被加载时会先委托父加载器加载父加载器加载不了才轮到子加载器。这样做的核心目的是保证类的唯一性防止用户自定义java.lang.String来覆盖JDK核心类。有没有打破过双亲委派这个问题最常见的例子是JDBC的SPI机制。JDBC的DriverManager是由启动类加载器加载的但它需要加载第三方数据库驱动而第三方jar包在classpath下启动类加载器根本加载不到。所以JDBC驱动加载采用了线程上下文类加载器在运行时动态指定加载路径这就在局部打破了双亲委派。Tomcat为了隔离不同Web应用的类也会用专门的WebAppClassLoader优先加载应用目录下的类。3.4 JVM调优与线上排查OOM真没那么神秘调优题一般结合场景问。最典型的问题是假如线上OOM你怎么排查。我比较推荐的回答路径是先用jps找到进程号用jmap -heap看一下堆使用情况用jmap -dump:formatb导出堆转储文件再用MAT或者VisualVM分析大对象如果是CPU飙高用top -Hp找到高CPU线程再用jstack导出线程快照把线程号转成十六进制后去栈里找对应的线程状态一般能看到GC线程或者业务线程的bug。常见OOM类型要记牢堆内存溢出对应java.lang.OutOfMemoryError: Java heap space元空间溢出对应Metaspace栈溢出对应StackOverflowError还有一类是创建线程太多导致的unable to create new native thread。回答这些时如果能顺带说出GC overhead limit exceeded这种冷门类型会显得你真的遇到过。我不建议死记一堆JVM参数但要能说出常用的几个-Xms设置初始堆大小-Xmx设置最大堆大小-Xmn设置新生代大小-XX:MaxMetaspaceSize设置元空间上限-XX:HeapDumpOnOutOfMemoryError表示OOM时自动导出堆快照。面试时讲到具体排障场景配合这些参数说出来会很有说服力。4. 并发编程必考中的必考4.1 线程基础与synchronized/volatile从状态到锁升级并发编程是Java面试的深水区也是拉开差距的主要模块。线程的生命周期必须先掌握新建New、就绪Runnable、运行Running、阻塞Blocked、等待Waiting、等待超时Timed Waiting、终止Terminated。注意Java线程状态在Thread.State里定义实际只有NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED六种RUNNABLE状态包含了操作系统层面的就绪和运行。synchronized在JDK 6之后有锁升级过程答题顺序是偏向锁、轻量级锁、重量级锁。偏向锁会记录持有线程的ID适合无竞争场景一旦发生竞争就升级为轻量级锁线程通过CAS自旋尝试获取锁如果自旋失败或线程数增加就膨胀为重量级锁依赖操作系统互斥量阻塞和唤醒之间要切换内核态开销很大。JDK 15之后偏向锁被废除但面试题库里的旧题目仍然存在回答时点一句偏向锁后续版本已经废弃反而加分。volatile要重点讲清楚两点可见性和有序性。可见性是因为写volatile变量时JMM会插入内存屏障强制把工作内存里的修改刷回主内存并使其他线程里该变量的缓存失效。有序性是可以禁止指令重排典型的应用是单例模式的DCL双重检查锁单例字段加volatile可以防止指令重排导致拿到半初始化的对象。但volatile不能保证原子性比如count操作本质上是读-改-写三步volatile只能保证这三步的可见性不能保证三步的原子性。4.2 JUC的核心组件Lock、AQS、并发工具ReentrantLock和synchronized的区别是必考题可以从四点回答ReentrantLock可以响应中断、支持超时、支持公平锁、可以绑定多个条件队列。底层实现依赖AbstractQueuedSynchronizerAQS。AQS是JUC的基石很多工具类都建立在它之上。AQS内部维护了一个volatile的state变量和一个CLH变体双向等待队列。以ReentrantLock为例抢锁就是通过CAS把state从0改成1抢锁失败就把当前线程封装成Node节点加入队列尾部然后通过LockSupport.park挂起线程解锁时把state改回0然后唤醒队列里的头节点后继线程。公平锁和非公平锁的区别在于非公平锁在进入AQS之前会先直接CAS抢一次锁抢不到再排队公平锁严格按队列顺序获取。CountDownLatch和CyclicBarrier也经常一起问。CountDownLatch是计数器减到0就放行用一次就没用了CyclicBarrier是让一组线程互相等待到齐后再继续可以循环复用。Semaphore是信号量控制同时访问资源的线程数可以拿来做限流。面试官如果追问AQS是怎么实现共享模式的能说出Semaphore和CountDownLatch用的是共享锁就够。CAS也一定会被问到。CAS是Compare And Swap通过Unsafe类提供的本地方法实现核心是用一个期望值和一个目标值去比较内存中的实际值如果一致就替换否则重试。CAS避免了线程阻塞但有两个典型问题ABA问题和自旋CPU开销。ABA可以用AtomicStampedReference解决给变量加版本号自旋开销大则是因为在高竞争下CAS会不断重试。4.3 线程池七个参数与拒绝策略线程池简直是面试出场率最高的题必须把ThreadPoolExecutor七个参数背得滚瓜烂熟核心线程数corePoolSize、最大线程数maximumPoolSize、空闲线程存活时间keepAliveTime、时间单位unit、等待队列workQueue、线程工厂threadFactory、拒绝策略handler。任务提交后的执行流程是先判断当前线程数是否小于核心线程数小于就创建新线程执行任务否则把任务放入队列等待如果队列满了再判断当前线程数是否小于最大线程数小于就创建新线程如果已经达到最大线程数就执行拒绝策略。这里有一个很多人都会记混的点是先放队列再到最大线程数而不是先创建到最大线程数再放队列。四种拒绝策略分别是AbortPolicy直接抛异常默认、CallerRunsPolicy调用者线程直接执行、DiscardPolicy直接丢弃、DiscardOldestPolicy丢弃队列里最老的任务。线程池参数怎么设置这是个开放题。CPU密集型任务可以把核心线程数设置为CPU核数加一IO密集型任务可以设置为CPU核数乘以二或者用公式CPU核数除以(1 - 阻塞系数)。如果是混合型任务更好的做法是拆分任务或者用多个线程池分别处理。回答时最好提到不要写死要根据压测结果动态调整。5. Spring、数据库与Redis项目背后的实力5.1 Spring IOC/AOP、事务把Bean生命周期讲透Spring框架在Java面试里的比重越来越高尤其社招面试很少只问纯语言一定会结合项目问框架原理。IOC和DI控制反转是指对象的创建和依赖关系的维护由容器来完成而不是由对象自己new核心思想是把控制权从代码转移到容器。依赖注入是实现控制反转的方式有构造器注入、Setter注入、字段注入三种方式。面试中常被追问Bean的生命周期完整的生命周期包括实例化、属性填充、初始化前回调如BeanPostProcessor的postProcessBeforeInitialization、初始化方法、初始化后回调postProcessAfterInitialization、销毁。其中AOP动态代理就是在BeanPostProcessor阶段生成的代理对象。AOP的底层机制要分清楚JDK动态代理和CGLIB。JDK动态代理要求目标类实现接口通过Proxy.newProxyInstance生成代理对象CGLIB通过生成目标类的子类来完成代理不要求实现接口。Spring Boot 2.x默认会用CGLIB代理。AOP概念里的切面、切点、通知、连接点至少要能用自己的话说清楚。Spring事务事务传播行为有七种最常问的是REQUIRED和REQUIRES_NEW。REQUIRED表示支持当前事务如果没有就新建REQUIRES_NEW表示挂起当前事务新建一个独立事务。Spring事务失效的几个常见场景也经常考方法不是public、自调用、发生异常被捕获、抛出受检异常而事务默认只回滚RuntimeException、数据库不支持事务机制等。自调用问题的本质是事务通过代理对象生效类内部方法调用走的是this不经过代理。5.2 Spring Boot自动装配与Spring MVC请求到底走了哪条路Spring Boot的自动装配原理也是大热题。答案可以从SpringBootApplication入手它由SpringBootConfiguration、EnableAutoConfiguration和ComponentScan组成。EnableAutoConfiguration通过Import导入AutoConfigurationImportSelector该类会扫描所有jar包里的META-INF/spring.factories文件读取里面配置的自动配置类再按条件注解ConditionalOnXxx逐个判断是否生效。Spring MVC的处理流程要能画出来并说清楚请求先到DispatcherServlet由它调用HandlerMapping找到处理器和拦截器再通过HandlerAdapter适配执行Controller方法返回ModelAndView经过视图解析器解析后渲染。现在前后端分离的项目基本都返回Json视图渲染环节可以简化但DispatcherServlet、HandlerMapping、HandlerAdapter这三个核心组件一定要说明白。过滤器和拦截器的区别也问得很多。过滤器是Servlet规范层面的作用于所有请求在DispatcherServlet之前执行拦截器是Spring MVC层面的只作用于Controller请求。过滤器先执行拦截器后执行两者都可以做登录校验、日志记录但拦截器可以拿到HandlerMethod等信息所以业务上用得更多。5.3 MySQL索引与事务隔离级别从B树到MVCC数据库方面索引是首要重点。InnoDB用的是B树索引为什么不是B树、不是红黑树关键的几个理由B树的非叶子节点只存索引不存数据单个节点能存储更多键值树高更低减少磁盘IO叶子节点之间用指针连接适合范围查询所有数据都在叶子节点查询路径长度固定性能稳定。要能顺着这个思路把为什么选择B树讲透。索引还要掌握聚簇索引和二级索引的区别。InnoDB的主键索引就是聚簇索引叶子节点直接存储整行数据二级索引的叶子节点存储的是主键值所以通过二级索引查询数据时往往需要回表。如果要查的字段正好都在二级索引里就能避免回表这叫覆盖索引是SQL优化里很常用的一招。事务隔离级别与MVCC四种隔离级别是读未提交、读已提交、可重复读、串行化。MySQL InnoDB默认是可重复读。MVCC多版本并发控制通过隐藏字段trx_id、roll_pointer和undo log实现读已提交和可重复读的区别在于生成ReadView的时机读已提交每次SELECT都生成新ReadView可重复读只在第一次SELECT时生成。这就是为什么可重复读能解决幻读的原理基础但InnoDB可重复读下幻读的彻底解决依赖间隙锁。锁InnoDB锁的类型包括共享锁、排他锁、表锁、行锁、间隙锁和next-key lock。行锁种类中记录锁锁住单条记录间隙锁锁住间隙next-key lock同时锁记录和间隙InnoDB默认的RR级别下会用它来防止幻读。5.4 Redis缓存基础穿透、击穿、雪崩一次讲完Redis基本不会缺席Java面试。首先要能说出五种基础数据结构String、Hash、List、Set、ZSet以及各自的典型场景。再往下问可能会问到底层编码和跳表。ZSet用跳表加哈希表实现跳表支持范围查询插入删除的时间复杂度是O(log n)实现难度比平衡树低。缓存三类经典问题是穿透、击穿、雪崩缓存穿透请求的数据缓存和数据库里都不存在每次请求都会打到数据库。解决方法是缓存空值、布隆过滤器。缓存击穿某个热点key过期瞬间大量请求同时打到数据库。解决方法是互斥锁、逻辑过期。缓存雪崩大量key在同一时间过期或Redis宕机导致数据库压力瞬间增大。解决方法是过期时间加随机值、热点数据不过期、多级缓存、高可用集群。持久化方面RDB是某个时间点的一次全量快照文件紧凑恢复快但可能丢失最后一次快照之后的数据AOF是追加操作日志数据丢失少但文件大、恢复慢。两者可以同时开启或以AOF为主。面试时如果能提一句4.0之后AOF支持混合持久化RDB作为base数据加上增量AOF日志兼顾加载速度和数据安全会让面试官眼前一亮。6. 场景题与算法八股文之外真正的分水岭6.1 场景设计题怎么答先定边界再选型很多基础题背得好的人在场景题这里突然卡壳。因为场景题考的不是记忆而是工程思维。我的建议是记住一个回答框架先明确需求边界再估算规模然后选型最后排查风险和扩展性。拿设计一个短链服务举例先问清楚是给内部用还是外部用、每秒QPS多大、短链有效期多久然后估算数据量比如每天1000万条新链接一年3.6亿条要用什么存储选型上短链生成的本质是发号器可以用数据库自增主键加进制转换也可以用预生成号段接着要考虑的是重定向用301还是302防攻击的限流怎么做过期清理怎么做。把流程讲完比给出一个唯一正确答案重要得多。其他高频场景题还有分布式锁怎么实现、接口幂等怎么做、消息丢失和重复消费怎么解决、秒杀系统怎么设计。分布式锁可以回答基于Redis的SETNX加过期时间、基于ZooKeeper的临时顺序节点。接口幂等可以用唯一索引、状态机、Token机制。消息队列的可靠投递要讲清楚生产端confirm机制、消费端手动ack、消息表、重试和死信队列。6.2 高频算法题怎么准备别只会刷题算法题不是八股文但算法题准备得怎么样往往决定你能不能进到后面的技术面。我的建议是不要盲目刷题先把高频类型吃透数组和双指针、字符串处理、链表操作、二叉树遍历、回溯、动态规划、排序和二分。排序算法中考得最多的是快排、归并、堆排序和基础的冒泡排序。冒泡排序虽然实际工程里几乎不用但作为入门题经常出现在笔试和面试里尤其是让手写并分析时间复杂度。要能快速写出稳定版冒泡外层循环控制轮数内层循环相邻比较每轮把最大元素冒到末尾时间复杂度O(n²)。在此基础上如果能说出优化点某一轮没有发生交换就提前结束就算过了。我见过不少候选人LeetCode刷了几百道但让他写一个单例模式都写不完整。所以算法之外手写题里单例模式、生产者消费者、手写一个LRU、手写线程安全的计数器这四类高频代码要多练几遍。特别是LRU最好能用LinkedHashMap实现一版再用双向链表加HashMap实现一版面试官看到你能写出第二种通常会很满意。7. 备考策略与经验分享7.1 面试题背下来就够吗说句实话面试越来越难不是因为题难而是因为面试官越来越会顺着问。你背了一个结论他马上追问一个为什么再追问一个你项目里遇到的是什么情况。所以只背题面答案远远不够一定要把每个结论背后的原理链条打通。有一些题是可以准备故事的。比如你简历上写了Redis缓存那就至少准备好三个故事缓存穿透是怎么出现的、怎么排查的、怎么解决的Redis挂了之后系统有没有降级方案缓存和数据库的一致性是怎么保证的。如果在讲项目时能自然引出这些细节比单纯背Redis的八股文要有说服力得多。7.2 学习路线与时间安排建议最后给一个我自己的备考节奏仅供参考。总共四周到六周比较合理。第一周把Java基础、集合、泛型、异常过一遍同时每天刷两道LeetCode热题。第二周主攻JVM和并发把内存模型、垃圾回收、AQS这些硬骨头啃下来。第三周主攻Spring、MySQL、Redis优先看自己简历上写过的东西。第四周刷场景题和高频算法同时开始做模拟面试找人互相问或者自己录音回听。第五周到第六周如果没有时间可以不做但如果面试时间允许再针对性补弱项。补充一条个人心得整理错题比刷题重要。我面试准备期间会维护一份错题文档把回答卡壳的题目、被追问倒的题目全部记录下来当天晚上重新讲一遍。有时候一顿饭的功夫就能把一个薄弱点补齐比闷头背一天书高效得多。我个人在实际准备面试时还有一个习惯在面试前一晚把所有核心知识点浓缩成一张一页纸不追求答案完整只记录关键词和触发点。比如AQS后面写state、CLH、LockSupport、CASHashMap后面写扰动、位运算、树化、尾插。第二天候场时快速过一遍思路会清晰很多。这个方法我也推荐给你八股文可以背但更重要的是在面试官面前展示出你是一个能独立思考、真正理解底层的工程师。