你自以为熟悉的Java,一问就倒
前几天帮朋友模拟面试,他简历上写着“精通Java”,三年经验。我随口问了一句:“HashMap的put方法,当发生哈希碰撞时,链表什么时候转红黑树?”他愣了三秒,开始磕磕绊绊地背源码行数。我又问:“那红黑树在什么情况下会退化回链表?”他直接沉默了。
这不是个例。Java面试真正的拦路虎,从来不是语法糖或者API用法,而是那些你自以为“会用就行”的底层原理。在一个技术团队里,真正决定代码质量的,往往是面试官能从这些核心问题里测出的思维深度。今天,我就以一个面试官的身份,把那些反复出现、一针见血的问题清单公开,并告诉你为什么这些问题能瞬间筛掉80%的候选人。
基础篇:String到底有多“不可变”
很多人张口就来:“String是不可变类。”那你问一句:String s = "abc"; s = s + "d";发生了什么?他可能会说“生成了新对象”,但再问“原始字符串对象被回收了吗?”就答不上来了。
真正的核心问题清单第一题:String的不可变性究竟保证了什么?答案远不止“不可修改”——它保证了字符串常量池的线程安全,保证了哈希码的缓存效率,甚至保证了类加载机制中的安全校验。如果你答不出“String的不可变性是JVM安全模型的基础之一”,说明你还没理解这门语言的底层设计哲学。
再问:StringBuilder和StringBuffer的区别,面试官真正想听的不是“前者线程不安全后者线程安全”,而是为什么单线程下StringBuilder更快?关键点在于:StringBuffer所有public方法都加了synchronized,而同步带来的锁竞争开销在单线程下是纯浪费。但更深入一层:JVM对StringBuffer做了锁消除优化吗?这就涉及逃逸分析了。
集合篇:HashMap是你心里的一根刺
几乎每个面试官都会问HashMap,但很少有人能扛住连环追问。核心问题清单第二题:HashMap在JDK 1.7和1.8中有什么区别?
99%的候选人会答:“1.7是数组+链表,1.8是数组+链表+红黑树。”然后就没有然后了。面试官想听的是:为什么引入红黑树?阈值为什么是8?链表查询时间复杂度O(n),红黑树O(log n),但红黑树节点更大,占用内存更多。当链表长度小于6时,红黑树的查找优势已经不足以抵消它的空间和旋转开销,所以阈值设计为8转为树,6退化为链表。中间的7是缓冲,防止频繁转换。
第三题:HashMap的扩容机制为什么是2的幂次?你可能会说“位运算取模效率高”——对了一半。更关键的原因是:扩容时元素在新数组中的索引要么在原位置,要么在原位置+旧容量。这个特性来自于hash & (newCap - 1)的计算方式,而旧索引是hash & (oldCap - 1)。如果扩容为2倍,那么hash值多出的那一位决定了位置偏移。这种设计使得扩容时不需要重新计算每个元素的哈希,只需要看新增的那一位是0还是1,极大提升了效率。
还有第四题:ConcurrentHashMap在JDK 1.7和1.8中分别怎么保证并发安全? 1.7用Segment分段锁,1.8用CAS+synchronized。为什么1.8放弃了分段锁?因为分段锁的粒度太粗,最多支持16个并发(Segment数量固定),而CAS+synchronized的粒度精细到每个桶,并发度更高。更重要的是:synchronized在JDK 1.6之后经过大量优化(偏向锁、轻量级锁),性能已经不输ReentrantLock。
并发篇:volatile不是玄学
第五题:volatile能保证原子性吗?这是经典的陷阱题。很多人说“能”,然后被反问“i++用volatile修饰是否线程安全”就露馅了。volatile只保证可见性和有序性,不保证原子性。i++是读-改-写三步操作,volatile无法阻止指令交错。但有一种情况例外:对volatile变量的单次读写操作(比如boolean标志位)是原子性的,因为64位基本类型(long/double)在32位JVM上可能分两次写入,volatile保证了其原子性。
第六题:synchronized和ReentrantLock的区别,面试官真正想听什么?千万别只背“前者是关键字,后者是类”。核心区别在于:synchronized是非公平锁,ReentrantLock默认非公平但可以设置为公平锁。但更本质的是:synchronized是JVM层面的锁,依赖monitor对象,而ReentrantLock是API层面的锁,内部通过AQS实现。后者提供了超时、可中断、多条件变量等特性。还有一个冷门但关键的点:synchronized在JDK 1.6之后引入了偏向锁、轻量级锁、重量级锁的升级过程,而ReentrantLock的lock()方法直接CAS抢锁,没有偏向机制。
第七题:ThreadLocal的内存泄漏问题,你处理过吗?很多人知道ThreadLocalMap的key是弱引用,但弱引用只针对key,value是强引用。如果线程一直存活(比如线程池里的线程),而ThreadLocal对象被垃圾回收了,那么key变成null,value却永远不会被回收。解决方案是在使用后手动调用remove()。但更深刻的思考是:为什么设计成弱引用?因为如果不设计成弱引用,即使程序员忘记remove,只要ThreadLocal对象还在(比如静态变量),key和value就都无法回收,造成更严重的内存泄漏。弱引用至少提供了一层保障:当ThreadLocal被GC后,key自动变null,下次get/set时可以清理。
JVM篇:垃圾回收不只是八股
第八题:CMS和G1的区别,别再背“低延迟、吞吐量优先”了。问:为什么CMS会引发“Concurrent Mode Failure”?因为CMS的并发清理阶段是和工作线程并发执行的,如果此时老年代空间不足,CMS不得不退化为Serial Old进行Full GC。而G1通过设置预期的停顿时间,将堆划分为多个Region,每次只回收一部分Region,避免全堆扫描。但G1也有缺点:当对象分配速度极快时,G1的混合回收可能追不上分配速度,导致Full GC。
第九题:你经历过哪些OOM?怎么分析的?常见的OOM有:堆内存溢出、栈内存溢出、元空间溢出、直接内存溢出。面试官想听的是你如何定位:用jmap dump堆,用MAT分析GC Roots引用链;或者用jstat看GC情况,用jstack看线程栈。真正加分的是你能否区分“内存泄漏”和“内存溢出”:泄漏是对象无用但被引用无法回收,溢出是对象确实需要但内存不够。泄漏的典型场景是ThreadLocal不remove、静态集合类不断添加元素。
第十题:类加载过程是“加载-验证-准备-解析-初始化”的哪一步?大多数人会背这个顺序,但问“什么时候会触发一个类的初始化?” 只有四个主动使用场景:new、反射、访问静态变量(非final)、子类初始化触发父类。被动引用不会触发初始化,比如通过子类引用父类的静态字段、定义数组引用等。还有一个更深的点:接口的初始化规则不同,只有使用接口中的非常量字段时才会触发。
Spring篇:依赖注入不只是注解
第十一题:Spring Bean的作用域有哪些?这个太基础,但面试官真正想问的是:request和session作用域在web环境下怎么生效的?底层原理是什么?答:通过代理模式,Spring为request和session作用的Bean创建代理对象,在每次调用时从当前请求/会话上下文中获取实际实例。核心技术是ObjectFactory的延迟查找。
第十二题:@Autowird和@Resource的区别?很多人说“前者按类型,后者按名称”。但@Resource是JSR-250标准,@Autowired是Spring专有。更关键的是:@Autowired可以用在构造函数上实现构造器注入,这是推荐的方式,因为构造器注入可以保证依赖不可变、不需要setter、且更容易检测循环依赖。循环依赖问题也是面试高频:为什么构造器注入解决不了循环依赖?因为构造器注入发生在Bean创建阶段,此时实例还未完全初始化,无法通过三级缓存(singletonFactories)提前暴露半成品对象。而setter注入可以,因为Spring通过三级缓存(EarlySingletonReference)解决了循环依赖。
第十三题:Spring事务的传播行为,你真的理解吗?REQUIRED、REQUIRES_NEW、NESTED……别只背定义。面试官会问:如果外层方法没有事务,内层方法有REQUIRES_NEW,会怎么样?答案:各自独立创建新事务。但更常见的坑是:同一个类中方法A调用方法B,B的@Transactional会失效。因为Spring事务通过AOP代理实现,方法A内部直接调用方法B(this.B())走的是原生对象,没有经过代理。解决方案是注入自身代理或使用AopContext.currentProxy()。
总结:面试官到底在找什么
这些核心问题清单,每一个都不是为了考倒你,而是为了检测你构建知识体系的能力。一个只会背八股的人,当被追问“为什么”的时候会漏洞百出。而一个真正理解底层的人,能从一处细节推演出整个设计逻辑。
当你下次面试时,如果面试官问“说说你对HashMap的理解”,不要从数组+链表开始背,而是直击灵魂地回答:“我认为HashMap最核心的设计是哈希算法与扩容机制之间的协同,它利用2的幂次特性避免了rehash,用红黑树来解决哈希碰撞严重时的退化问题。”——这才是面试官想听到的答案。
最后送你一句我作为面试官最想说的话:别把Java当工具,把它当一门可以无限深入的艺术。每一次底层原理的穷追,都在帮你从“API调用者”进阶为“架构设计者”。希望这份清单能成为你下一场面试的转折点。