ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Java面试高频考点拆解:从源码原理到分布式架构实战

Java面试高频考点拆解:从源码原理到分布式架构实战 1. 2026年的Java面试,到底在考什么说句实话,每年三四月一到,后台私信问“Java面试题怎么准备”的人就格外多。2026年4月这个时间点,我翻了一圈大家搜得最多的关键词:Java基础、java面试八股文、Spring Boot、MySQL、Redis、Kafka、分布式锁,还有一堆Linux、前端Vue3、软件测试的题也混了进来。这说明一个很现实的问题:面试早就不是“背几道题就能过”的时代了,面试官自己也进化了,越来越喜欢往实际场景里问,追着你的回答一层一层往下挖。这篇内容我不打算给你一份“标准答案大全”,那种资料网上到处都是,背完第二天就忘。我更想做的,是把当前这个节点上Java面试最高频的考点拆开揉碎,讲讲每道题背后的原理、面试官到底想听到什么、哪些地方容易翻车,再加上我自己这些年面试别人和被别人面试攒下的一些经验。不管你是准备校招、跳槽,还是刚转行想入坑Java,这篇都可以当成一份“面试前必刷清单”来用。内容涉及的面比较宽,包括Java基础与JVM、并发编程、Spring家族、MySQL和Redis、分布式与消息队列、算法手写题,顺带把热词里的Linux、前端、测试方向也带一下,尽量做到有代表性。需要提前说明的是,面试题没有绝对标准的答案,同一个问题不同面试官的理解也可能不一样。我下面整理的这些,综合了最近招聘市场上普遍的考察套路,再加上大家搜索热度最高的方向。你要是能在理解的基础上,结合自己的项目经历给出更具体的回答,那效果会比背任何现成答案都好得多。2. Java基础与JVM:最容易翻车的高频区2.1 集合框架:ArrayList、HashMap源码你真的读懂了吗集合是Java面试的入门必问,但很多人挂在同一个地方:只背结论,不讲原理。比如面试官问“HashMap的put流程是什么”,你如果只说“根据hash找到桶位置,冲突就链表或红黑树”,这个回答只能拿及格分,拿不到亮点分。真正的答题思路是这样的:先讲hash算法的本质,也就是(key null) ? 0 : (h key.hashCode()) ^ (h 16),这么设计是为了让高位与低位做异或,让高位也参与取模运算,从而降低哈希冲突概率。然后讲put的完整流程:计算hash - 判断table是否为空,空则先resize初始化 - 根据hash定位到桶 - 桶为空直接插入 - 桶不为空则判断首节点key是否相同,相同则覆盖 - 如果是TreeNode就按红黑树插入 - 否则遍历链表,找到相同key就覆盖,找不到就尾插,同时判断链表长度是否达到8且table长度达到64,满足才转成红黑树。最后再补一句什么时候触发resize、负载因子默认0.75的原因。这里有个细节经常被考:为什么链表转红黑树的阈值是8,而红黑树退化为链表的阈值是6?网上有人说是“时间复杂度的平衡点”,其实源码注释里给过解释:根据泊松分布,在负载因子0.75、随机hash的情况下,一个桶里链表长度超过8的概率大约是千万分之六,所以8是基于统计和性能综合考量的经验值。而退化阈值选6而不是7,是为了留出缓冲,避免链表和红黑树在边界上来回切换,白白消耗性能。能把这个细节答出来,面试官对你的印象分立刻不一样。ArrayList这边,高频考点是扩容机制:默认容量10,扩容时newCapacity oldCapacity (oldCapacity 1),也就是原来的1.5倍。扩容靠Arrays.copyOf,底层调用System.arraycopy这个native方法,注意这是浅拷贝。还有一个经典对比题:ArrayList和LinkedList的区别。很多人只答“一个数组一个链表”,但面试官真正想听的是三个维度:内存结构、增删改查的时间复杂度、对CPU缓存的利用。ArrayList随机访问是O(1),LinkedList是O(n);在头部插入上ArrayList要移动后面所有元素所以是O(n),LinkedList是O(1);但现代CPU有缓存行机制,ArrayList的连续内存在遍历时反而比LinkedList快很多,因为可以预取到缓存中。这个点能说出来,比单纯背区别就有深度多了。2.2 JVM内存与垃圾回收:OOM不是一句“内存不够”就能糊弄的热词里有一个非常有代表性的报错:java: outofmemoryerror: insufficient memory。这个错误在面试里经常被包装成场景题来考,如果你只回答“内存不够了,调大Xmx就行”,那基本就凉了。正确的排查思路是分层的:先确认是堆内存OOM还是非堆内存OOM。堆内存OOM要分清是Java heap space、GC overhead limit exceeded还是Metaspace。Java heap space优先用jmap -dump:formatb,fileheap.hprof pid抓堆转储,然后用MAT或JProfiler分析谁占了内存大头,定位到具体的业务代码场景。GC overhead limit exceeded的意思是GC回收效率太低,花了98%的时间在GC却回收不到2%的内存,这种情况先看代码里是不是有循环创建大对象或者大List没释放,再看堆是不是真的设置得太小。Metaspace爆了通常是动态生成类太多,比如大量CGLIB代理、反射生成类、热部署场景,老项目里最常见的就是引了太多第三方框架还没做类加载隔离。垃圾回收这一块,现在面试官的关注点已经从“背CMS和G1的区别”变成了“你项目里用的什么回收器,为什么选它,参数怎么调的”。如果你的项目还在JDK 8,老老实实说G1就已经能过关;如果在JDK 17以上,可以聊聊ZGC以及JDK 21虚拟线程对GC停顿的改善,但同时得注意,不要为了显得高深就堆概念,要结合自己的项目场景说:堆多大、预期的请求量级、对延迟的敏感程度,这些才是面试官真正想听的具体内容。热词里还有一个JDK编译的坑:java: 警告: 源发行版 17 需要目标发行版 17。这个问题在面试里偶尔会演变成一道环境排查题:IDEA里为什么会报这个错?本质上就是maven-compiler-plugin的source和target版本与当前JDK版本不匹配,或者IDEA的Project Structure里Project SDK和Java Compiler的Target bytecode version不一致。解决办法要么统一在pom.xml里配置maven.compiler.source和maven.compiler.target,要么更高阶一点,直接用release17/release来同时限定源码和字节码版本。这种问题虽然基础,但在线上面试的共享屏幕环节,出镜率真不低。2.3 String、异常、泛型与Lambda:基础题也有深水区String有两道经典题:一是String a abc; String b new String(abc);为什么a和b用比较是false,因为前者走字符串常量池,后者在堆上创建对象。二是String为什么设计成不可变,这个要答出三点:安全(字符串常被用作类名、网络地址、配置项,可变会带来隐患)、常量池复用(只有不可变,才能安全地在常量池里共享对象)、线程安全(不可变对象天然线程安全,不需要额外同步)。能答出这三个层次,基本就过关了。异常这块别只背“Error和Exception的区别”,要会画异常分类的树状结构,更要会回答“什么时候用受检异常、什么时候用非受检异常”。我的建议是:业务上可以恢复的、调用方必须处理的,用受检异常;程序bug、参数错误这类不可恢复的,用非受检异常,比如IllegalArgumentException和NullPointerException。不过现在的项目里,新代码其实已经不怎么用受检异常了,更多是用Result对象统一封装错误码,这个趋势在面试里也可以聊一聊,显得你不仅在应试,还在关注行业实践演进。泛型问得最多的是List? extends T和List? super T的区别,也就是PECS原则(Producer Extends, Consumer Super):如果你要从集合里往外读元素,用extends;如果你要往集合里写元素,用super。Lambda和函数式接口也是热词里的高频点,面试官喜欢问:FunctionalInterface的作用是什么、Stream的map和flatMap区别、方法引用有哪四种类型。这类题目不难,但容易答得零散,建议整理成体系来备:函数式接口 - Lambda语法 - Stream操作分类(中间操作和终止操作) - 常见的流式处理陷阱。这里有个好素材,比如parallelStream在什么场景下反而更慢——数据量小、CPU密集、有共享可变状态时,并行流的分片和合并开销可能会抵消并行收益,这个细节说出来就会显得你踩过坑。还有一个热词我特别想说:java中数组越界异常(ArrayIndexOutOfBoundsException)。乍一看太基础了对吧?但它恰恰是很多中高级程序员都容易踩的坑——比如在for循环里用了i list.size()而不是i list.size(),或者多线程环境下遍历集合的同时,另一个线程正在remove元素。面试考这个往往不是考你认识不认识这个异常,而是考你怎么避免它:优先用增强for或者Iterator,需要边遍历边删除时用Iterator.remove()而不是list.remove()。Java里也有fail-fast机制,ArrayList的modCount和迭代器的expectedModCount不一致时就会抛ConcurrentModificationException,这是另外一个常考点,可以串在一起准备。3. 并发编程与多线程:高并发场景的核心考察点3.1 synchronized与ReentrantLock怎么选这道题问的人太多,但答得好的人太少。大多数人的答案停留在“synchronized是JVM层面的锁,ReentrantLock是JDK层面的锁”——这相当于只说了一个字面结论,面试官想听的是更具体的差异点和选型逻辑。完整的对比至少应该包含这几个维度。底层实现上,synchronized依赖Monitor监视器,由JVM管理,而ReentrantLock基于AQS抽象队列同步器。功能上,ReentrantLock支持可中断获取锁(lockInterruptibly)、支持超时获取锁(tryLock(timeout))、可以指定公平策略,还能绑定多个Condition条件队列实现分组唤醒,这些synchronized都不支持。性能上,经过长期优化,synchronized有了偏向锁、轻量级锁、重量级锁的升级路径,和ReentrantLock在普通竞争下的性能差距已经非常小,所以我的选型原则是:如果没有中断、超时、公平锁、多条件队列这些高级需求,直接优先用synchronized,代码更简洁,也不会犯“lock后忘记unlock导致死锁”的低级错误。如果面试官继续往下追AQS的原理,你应该能说清楚这几个点:AQS内部维护一个volatile的state变量和一个基于CLH变体的FIFO双向等待队列。acquire方法先尝试tryAcquire修改state,失败就把当前线程封装成Node节点放入队尾,并通过LockSupport.park挂起线程;释放锁时唤醒head节点的后继线程。ReentrantLock的可重入是怎么实现的?就是当前线程再次获取锁时state自增,释放锁时state自减,减到0才真正释放锁。这个机制听起来简单,却是整个JUC并发包的基石,值得花时间吃透。3.2 volatile、CAS、ThreadLocal的原理与坑volatile有“三必问”:保证可见性、禁止指令重排、不保证原子性。前两个是它存在的意义,第三个是面试官用来挖坑的。为什么禁止指令重排很关键?最经典的应用就是DCL双重检查锁单例模式,instance new Singleton()这行代码在字节码层面可以拆成“分配内存-初始化对象-将引用赋值给变量”三个步骤,如果不加volatile,JVM指令重排后可能出现“先赋值引用、后初始化对象”的情况,另一个线程此时拿到引用,却读到未初始化的半成品对象。CAS(Compare And Swap)的原理要往底层说:它依赖CPU的cmpxchg指令,JDK里通过Unsafe类封装成本地方法调用,AtomicInteger等原子类就是靠它实现的。CAS最大的坑是ABA问题:值从A变成B又变回A,再来一个线程执行CAS时发现值还是A,就误以为“没人动过”。解决办法是加版本号或时间戳,JDK自带的AtomicStampedReference就是干这个的。面试官还喜欢问:JUC里为什么还要有LongAdder,它在高并发下为什么比AtomicInteger快?因为LongAdder把单一value拆成了一个base加一个Cell数组,多个线程分散到不同Cell上各自累加,最后sum时再把所有值加起来,这样显著减少了CAS冲突。能把这个演进逻辑讲清楚,说明你真的理解并发包的设计思想,而不只是背类名。ThreadLocal这个考点要答出三个层次:用法(每个线程一份独立变量副本)、原理(每个Thread内部有ThreadLocalMap,key是ThreadLocal的弱引用,value是强引用)、内存泄漏(为什么弱引用key还会泄漏——因为value是强引用链,线程池里的线程长期存活,value就一直没有被回收)。对应解决办法也有两个:一是每次用完主动remove(),二是用static修饰ThreadLocal变量,延长它的生命周期,避免频繁创建导致map里堆积过多无效entry。热词里还有个“java定时任务框架”,这个也常和并发串在一起考:Scheduled注解的底层是ScheduledThreadPoolExecutor,基于DelayQueue实现到期任务的调度,而不是简单地开一个死循环,能把这个底层说清楚,面试官就知道你不是只会用注解。3.3 线程池参数:核心线程数到底怎么定线程池的七个参数(corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler)背出来不难,难的是“核心线程数到底怎么定”。网上流传着“CPU密集型设N1、IO密集型设2N”的经验法则,但说实话,这个法则能让你过及格线,不能让你拿高分。更好的回答方式是分层递进。如果是CPU密集型任务,核心线程数设为CPU核心数加1;如果是IO密集型任务,线程数可以设大一些,因为线程大部分时间在等待IO;但更科学的做法是基于Brian Goetz提出的估算公式:线程数 CPU核心数 * (1 等待时间 / 计算时间)。先估算每个任务里等待和计算的比例,再带入公式,得到一个初始参考值,最后通过压测验证和调整。这个公式能回答“网上说2N到底怎么来的”——如果等待时间和计算时间相等,公式结果就是2倍的CPU核心数。能说到这层,面试官基本就会觉得你做过有深度的性能设计,而不是只会抄公式。拒绝策略也是常考点,四种策略要分清:AbortPolicy是默认策略,直接抛异常;CallerRunsPolicy用调用者线程执行任务,适合不想丢任务的场景,但会导致调用线程阻塞;DiscardPolicy静默丢弃;DiscardOldestPolicy丢弃队列里最老的任务。实际项目里我比较推荐CallerRunsPolicy,尤其在Web应用里,能起到轻量背压的作用;同时要提醒一句,如果使用CallerRunsPolicy,你的调用线程可能被阻塞在任务里,主流程的响应时间可能会被拉长,需要提前评估好队列容量和最大线程数。4. Spring家族与Spring Boot:框架题背后的设计思想4.1 Spring Bean生命周期与循环依赖Bean生命周期这道题,建议答出完整过程:实例化 - 属性填充 - Aware回调(BeanNameAware、BeanFactoryAware、BeanClassLoaderAware) - BeanPostProcessor的postProcessBeforeInitialization - InitializingBean的afterPropertiesSet - 自定义init-method - BeanPostProcessor的postProcessAfterInitialization - Bean正常使用 - 销毁阶段(DestructionAwareBeanPostProcessor、DisposableBean、自定义destroy-method)。面试官考这条链路,不只是考记忆,更是看你能不能理解Spring AOP代理为什么恰好发生在postProcessAfterInitialization阶段——因为代理对象的创建和包装就出现在这个位置。如果你还能说出“PostConstruct注解方法是在postProcessBeforeInitialization之后执行的,它的底层是CommonAnnotationBeanPostProcessor”,那就更扎实了。循环依赖是真正的区分度题目。核心要答出:Spring通过三级缓存解决单例Bean的循环依赖。一级缓存singletonObjects存放成品对象,二级缓存earlySingletonObjects存放早期暴露的半成品对象,三级缓存singletonFactories存放ObjectFactory工厂。流程是:A创建时发现需要B,先把A的ObjectFactory暴露到三级缓存,然后去创建B;B创建时发现需要A,从三级缓存拿到A的工厂,把A提前放入二级缓存并完成注入;B创建完成后,再回到A,把A的其余属性补齐。这里有个必须想明白的点:为什么一定要三级缓存,二级行不行?因为三级缓存存的是ObjectFactory,在需要动态代理时,可以在这里通过getEarlyBeanReference提前包装出代理对象,保证提前暴露的对象和最终注入的对象是同一个。但如果你答到这里,面试官很可能会追加一句:为什么Autowired字段注入能解决循环依赖,构造器注入却不行?因为构造器注入在实例化阶段就要传入依赖,此时对象本身还没创建出来,三级缓存还没机会暴露它,所以无解。另外,prototype作用域的Bean以及Async注解的Bean默认也不支持循环依赖,这两个细节很容易被忽略,记一下。4.2 Spring Boot自动配置原理自动配置是Spring Boot的招牌考点,答题主线非常清晰:SpringBootApplication是SpringBootConfiguration、EnableAutoConfiguration、ComponentScan三个注解的组合。EnableAutoConfiguration通过Import(AutoConfigurationImportSelector.class)导入一批自动配置类,AutoConfigurationImportSelector会读取类路径下的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件(Spring Boot 2.7之后的位置,老版本是spring.factories),把所有候选自动配置类列出来,再根据ConditionalOnXxx这一组条件注解逐个判断是否生效。比如ConditionalOnClass判断classpath下有没有对应的依赖类,ConditionalOnProperty判断配置项有没有开启。这就是为什么你引入Redis依赖后Spring Boot能自动装配RedisTemplate,移除依赖后所有相关配置自动失效。热词里还有springboot面试题和spring面试题,如果时间只够准备两道Spring题,我建议准备这道自动配置原理,再加一道AOP题。AOP的考点是动态代理的两种方式:JDK动态代理要求目标类实现接口,基于反射的Proxy和InvocationHandler;CGLIB代理则通过继承目标类、用ASM字节码生成子类来实现。Spring Boot 2.x之后默认使用CGLIB,proxyTargetClasstrue,所以即使目标类没有接口也能代理。同时要能说出Transactional的失效场景,这道题出现率极高:方法不是public、同类内部自调用不走代理、异常被catch吞掉、检查型异常默认不触发回滚、类没有被Spring容器管理。这五个场景,至少要说出来三个,并且最好配合一个自己遇到过的小例子。4.3 MyBatis与ORM:面试里容易忽略的细节MyBatis的面试题集中在几个点:#{}和${}的区别、一级缓存和二级缓存、Mapper接口的原理。#{}走预编译参数绑定,能防止SQL注入;${}是字符串直接拼接,存在注入风险,但也能用来动态指定表名、列名和排序字段。一级缓存是SqlSession级别的,默认开启,同一个SqlSession内重复查询相同语句会命中缓存;二级缓存是namespace级别的,需要手动开启。Mapper接口能直接使用,是因为Spring通过MapperProxy的JDK动态代理,把接口方法映射到了XML或注解里的SQL执行逻辑。这里有个容易翻车的点,很多人说${}完全不能用,这其实不严谨。${}确实有SQL注入风险,所以业务参数一律要用#{};但如果你需要动态传表名、ORDER BY的字段名,或者拼接固定的SQL片段,比如数据权限里的组织机构ID列表,那就必须用${}。关键不在于“用不用”,而在于“拼进去的内容是否受控”,比如表名经过白名单校验、排序字段通过枚举映射,而不是直接拿用户输入去拼。能把这个细节答出来,面试官基本就认可你写过真实的生产代码,而不是只在教程项目里跑过demo。5. 数据库与缓存:MySQL、Redis是分水岭5.1 MySQL索引、事务、锁的必问题MySQL在Java面试里的地位已经不亚于Java本身。索引这块,必问B树为什么适合做数据库索引,分析时要把B树、红黑树、哈希表三个对比对象拉出来:二叉树深度太大导致磁盘IO次数太多,哈希索引不支持范围查询,而B树的叶子节点存数据且形成有序链表,树的高度低、范围扫描能力强,这就是InnoDB选择B树的核心原因。还要能讲清楚聚簇索引和二级索引的区别:InnoDB的表数据本身就是按主键索引的B树组织的,二级索引的叶子节点存的是主键值,所以使用二级索引查询时,如果需要的列不在索引里,就要再用主键回到聚簇索引上查一次,这个动作就叫“回表”。事务这块,ACID四个特性打底,然后重点讲隔离级别:读未提交、读已提交、可重复读、串行化。InnoDB默认是可重复读,通过MVCC实现快照读,通过next-key lock(记录锁加间隙锁的组合)解决幻读问题。这里有个值得背的细节:MVCC依赖隐藏字段DB_TRX_ID和DB_ROLL_PTR,配合undo log实现多版本链,ReadView会在读的时候判断当前事务能看到哪个版本的数据。能把这个链路讲完整,已经是中高级水平了。锁的考点和热词里的分布式锁面试题要区分开。MySQL层面的行锁、表锁、间隙锁、死锁排查属于数据库锁,跨进程协调资源的才叫分布式锁。MySQL死锁排查是高频场景题,要会说:show engine innodb status查看最近一次死锁信息,information_schema.innodb_trx查当前事务,information_schema.innodb_lock_waits查锁等待关系,配合pt-deadlock-logger这类工具做长期监测。如果面试官追问“死锁怎么避免”,可以从业务层面加锁顺序一致化、缩短事务时间、合理设计索引减少锁范围几个角度回答。5.2 Redis缓存穿透、击穿、雪崩与分布式锁Redis相关题,热词里能同时搜到redis面试题和分布式锁面试题,可见这两个方向火到什么程度。缓存三大问题是必考中的必考,而且很多人会在“穿透”和“击穿”上翻车,我这里先把定义理清楚:穿透是查询一个不存在的key,导致请求直接打到数据库;击穿是某个热点key在失效的一瞬间,大量请求同时打到数据库;雪崩是大量key同时失效,或者Redis实例整体不可用,导致数据库被压垮。解决方案分别是:穿透用布隆过滤器或缓存空值,击穿用互斥锁或逻辑过期方案,雪崩的基础做法是给过期时间加随机值,更进一步的思路是热点数据永不过期加后台异步更新,或者搭建多级缓存。分布式锁的完整回答链应该分三步。先说明为什么需要分布式锁:本地锁synchronized在多实例部署下各自独立,锁不住跨进程的共享资源。再讲三种实现方案,并给出对比。方案一是Redis SETNX,核心命令是SET lockKey requestId NX PX 30000,注意两个关键点:value必须是请求唯一标识,防止一个线程误删另一个线程的锁;必须设置过期时间,防止持有锁的线程宕机导致死锁。释放锁时不能直接DEL,要先判断value是不是自己,再用Lua脚本保证“比较删除”的原子性。方案二是Redisson,通过看门狗机制对锁自动续期,解决业务执行时间超过锁过期时间的问题,但要注意主从切换时锁可能短暂丢失。方案三是ZooKeeper,基于临时顺序节点加监听机制,能提供严格意义上的强一致性,性能却不如Redis。如果要说一个“有深度”的结尾,就提一句:Redis主从架构下的锁存在丢失风险,对强一致要求高的场景可以考虑RedLock方案或者直接用ZooKeeper,没有银弹,按业务场景取舍。5.3 缓存一致性:哪些方案真的能用缓存一致性问题比三大缓存问题更有深度,也是现在的面试官非常喜欢在项目场景里追着问的话题。核心矛盾就一句话:数据库更新了,怎么保证缓存里的旧数据不被读到?最常见的方案是Cache Aside(旁路缓存):读的时候先读缓存,没命中就查数据库再回填;写的时候先更新数据库,然后删除缓存。为什么是“删缓存”而不是“更新缓存”?因为并发环境下,多个线程同时更新缓存,很容易把旧值覆盖成新值再被另一个旧值覆盖,形成脏数据;而删除缓存配合懒加载,下次读取时从数据库重建,更安全。但删缓存也有问题:删完后、重建前的窗口期,如果有一个并发请求读到了旧数据并回填缓存,就会导致脏读。针对这个窗口,业界惯用的招数是延迟双删(删除缓存 - 更新数据库 - 休眠一小段时间 - 再删一次),或者通过订阅binlog的方式(Canal)异步删除缓存。Write Through和Write Behind(异步写回)也有各自的适用场景,但Write Behind要接受数据可能丢失的风险,生产环境一般还是Cache Aside为主。回答的收尾最好是:没有银弹,要根据业务对一致性的容忍度和并发压力做取舍,能说出这句话,面试官就觉得你是有实战判断力的。6. 分布式与消息队列:从八股走向场景6.1 Kafka怎么答才有深度kafka面试题在热词里的搜索量一直很高。Kafka的基础题包括:分区与副本机制、生产者消息发送流程、消费者组与分区分配、offset提交机制、ISR机制、消息不丢失/不重复/不顺序三个“不”。但按我的面试经验,现在的面试官更愿意把Kafka放进具体场景里问,而不是单纯抽概念。一个高频场景题是这样:订单系统的消费者在处理消息时,业务逻辑抛异常了,这条消息怎么办?正确思路是先判断异常类型。如果是可重试的,比如依赖的下游服务短暂不可用,就用重试策略配合重试Topic,在Spring Kafka里对应DefaultErrorHandler的retry配置,设置重试次数和间隔,超过重试次数后进入死信队列;如果是不可重试的,比如消息本身格式错误,直接记录完整日志、送入死信队列,再通过定时任务或人工补偿处理。千万不能说“catch异常打日志就完事了”,那样消费位置已经提交,消息就永远丢了。回答里如果能提到“消费位置提交是Kafka不丢消息的关键一环,要区分自动提交和手动提交”,那就更完整了。另一个常考题是Kafka为什么快。要答出四个核心原因:顺序写磁盘(现代机器上磁盘顺序IO的速度可以接近内存随机读写,这是Kafka高性能的基础)、页缓存(消息先写入操作系统的Page Cache而不是JVM堆,减少GC压力)、零拷贝(利用sendfile和mmap减少用户态与内核态之间的数据拷贝次数)、批量处理(生产者批量发送、消费者批量拉取、broker批量存储)。这四个点覆盖了写入、缓存、网络传输和IO四个维度,能把每个点展开说一两句,就已经比绝大多数候选人强了。6.2 分布式事务:2PC、TCC还是最终一致性分布式事务是Java高级岗的必问题,不管热词里搜的是springboot面试题还是kafka面试题,都容易牵扯到这里。基础的三板斧要能说清楚:2PC两阶段提交,协调者先询问所有参与者能否prepare,全部成功后再统一commit,缺点是同步阻塞、协调者单点、第二阶段可能出现不一致;TCC(Try-Confirm-Cancel)把业务拆成三个阶段,业务侵入比较大,每个操作都要写三套逻辑,适合对一致性要求高的场景;本地消息表加MQ最终一致性最务实,核心思路是把“写业务数据”和“写消息表”放在同一个本地事务里,然后通过定时任务扫描未发送的消息,把消息可靠地投递到MQ,下游消费成功后回调确认。站在当下这个时间点的面试角度,我更推荐你额外准备一下Seata的AT模式,因为它几乎是现在企业落地的标准答案。AT模式通过全局事务ID加分支事务,配合undo_log里记录的before镜像和after镜像,自动生成反向SQL来补偿,对业务代码改动非常小,是一种无侵入的最终一致性方案。答题顺序可以是:先把2PC、TCC、消息最终一致性讲一遍,然后结合自己的项目说“为什么我选了最终一致性而不是强一致”,比如订单创建后扣库存、加积分这类场景,用户能容忍几秒钟的延迟,但对系统吞吐量有明确要求,用最终一致性是合理的取舍。能说出“取舍”二字,面试官才会觉得你有架构思维,而不是背了一堆名词。7. 算法与手写题:冒泡排序都能暴露问题7.1 高频手写代码题清单热词里冒泡排序java赫然在列,这算是热度最高的单一算法词了。别觉得“这么简单不可能考”,我当面试官的时候,真的见过很多人在白板上写冒泡排序写出死循环或者边界错误。冒泡排序的标准写法是:public static void bubbleSort(int[] arr) { if (arr null || arr.length 2) { return; } for (int i 0; i arr.length - 1; i) { boolean swapped false; for (int j 0; j arr.length - 1 - i; j) { if (arr[j] arr[j 1]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; swapped true; } } if (!swapped) { // 这一轮没有发生交换,说明数组已经有序,提前结束 break; } } }这个版本加了swapped标记,是优化过的冒泡排序,最好情况下时间复杂度可以到O(n)。面试官考排序不只是看你会不会写,还会让你分析时间复杂度和稳定性:冒泡稳定、快排不稳定、归并稳定,这些要能够脱口而出。最好
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表