面试官手里那张纸,写满的不是题目,而是你的漏洞。他们从Java基础一路问到并发,从来不是靠背诵题库,而是沿着一条隐形的技术主线——从数据结构到内存模型,从锁机制到调度策略。这8个知识点,是面试中出现的最高频的锚点。每个锚点背后都藏着一种思维方式的考察:你有没有在“会用”之外,真正理解“为什么”。下面这篇长文,带你从基础打到并发,看看那些容易翻车的细节。
从HashMap开始,面试官想听什么?
HashMap几乎是Java面试的第一道开胃菜,但很多人死在它的底层原理上。面试官问“HashMap的原理”时,他其实想知道三件事:hash函数的本质是均匀映射,而不是随机;数组+链表+红黑树的结构是为了解决hash冲突的不同烈度;扩容为何要重新计算下标。这三件事你能否用一句话说清,决定了他对你基础能力的判断。
很多人背得下加载因子0.75,说得出链表转红黑树的阈值是8,却答不出为什么是8。0.75是空间与时间的一个妥协,不是数学上的最优解。如果你能顺带提到泊松分布下链表长度达到8的概率极低,那么面试官会认为你不仅看了源码,还懂一点概率论。但更关键的坑在后面:HashMap为什么线程不安全?
死循环的案例在JDK7真实存在,因为头插法在并发扩容时会形成环;JDK8改成了尾插法,死循环没了,但数据丢失依然可能发生。HashMap的线程不安全,本质上是“并发一致性”问题,而不是“死锁”问题。你要能说出:多个线程同时put导致size覆盖,同时扩容导致数据丢失,这才是面试官愿意听到的答案。
并发容器的答案,从不背源码开始
HashMap不安全,那就上ConcurrentHashMap。面试官对ConcurrentHashMap的期待,绝对不只是“它用了分段锁”或者“它用了CAS”。你要分清JDK7和JDK8的架构差异。JDK7的分段锁是粗粒度的悲观锁,JDK8则直接用synchronized锁住桶的头节点,并发粒度更细。很多人误以为JDK8完全抛弃了锁,其实没有,它只是在无竞争时用CAS,有竞争时才升级为synchronized。
这里有个更高级的考点:为什么JDK8要把分段锁换成synchronized?除了内存占用的考虑,还因为synchronized在JDK6之后经历了锁升级优化,性能已经不输ReentrantLock。ConcurrentHashMap的size()方法,在并发下如何不锁整个map就拿到近似准确的数值?答案是分段计数+CounterCell数组。如果你能答出“先无锁CAS累加,冲突时加锁扩容计数数组”,面试官会眼前一亮。
另外,听说过多线程下使用HashMap导致的“fail-fast”吗?ConcurrentHashMap的迭代器是弱一致的,它允许迭代过程中并发修改,这一点也常被问。记住这句话:弱一致是并发容器用一致性的牺牲换来了吞吐量的提升。
JVM内存模型不是背下来就行
接下来进入JVM运行时的领域。面试官常问:“Java虚拟机怎么管理内存?”如果你直接背出“五块区域:程序计数器、虚拟机栈、本地方法栈、堆、方法区”,那只能算及格。他真正想看到的是你对“线程私有”和“线程共享”这两个概念的理解。程序计数器是唯一不会OOM的区域,因为它记录的是字节码行号,这个细节很多人不知道。
栈上的每个栈帧对应一个方法调用,局部变量表、操作数栈、动态链接、返回地址——这些概念你自己得先理清。堆又分成新生代和老年代,新生代又继续拆出Eden和Survivor,但这只是分代垃圾收集器的设计,不是JVM规范强制要求的。很多面试官会故意问“方法区会不会OOM?”答案是会,而且JDK8用元空间代替了永久代后,元空间用本地内存,依然可能耗尽。
对象创建的过程也不能含糊:加载类、分配内存、初始化零值、设置对象头、调用构造方法。其中分配内存有“指针碰撞”和“空闲列表”两种方式,并发分配内存怎么办?答案是CAS失败重试或者TLAB(线程本地分配缓冲)。如果能从对象创建一路讲到安全点,说明你对JVM基础已经形成了网状的记忆,而不是孤立的知识点。
GC是面试官最喜欢挖坑的地方
垃圾回收不是“把不用的对象删掉”那么浪漫。面试官最爱问的三个问题:哪些对象可以被回收?如何回收?用什么收集器回收?第一个问题核心是可达性分析,GCRoots包括栈中引用的对象、静态变量、JNI引用等。注意:循环引用并不会导致内存泄漏,因为可达性分析根本不用引用计数,这是初学者最容易犯的错。
第二个问题里的标记-清除、标记-复制、标记-整理,你要能画出图来。为什么新生代用复制算法,老年代用标记-整理或标记-清除?因为新生代“朝生夕死”的比例高,复制算法高效。Stop The World是GC的刺,所有收集器都在想办法缩短它。CMS通过初始标记、并发标记、重新标记、并发清除四步,把最耗时的部分并行化;但CMS会产生浮垃圾,而且它本质上一个“标记-清除”算法,内存碎片严重。
更现代的G1把堆分成Region,维护一个可预测的停顿时间。面试官如果问你:G1的RSet是做什么的?如果你能答出“用于记录Region之间的引用关系,避免全堆扫描”,那你就和普通背八股的人拉开了差距。垃圾收集器的选型不是越多越好,而是看你的业务对停顿时间是否敏感。
双亲委派模型,你真的理解了吗?
类加载机制听起来像理论,其实是排查ClassNotFoundException和NoSuchMethodError的武器。加载、验证、准备、解析、初始化,五个阶段要分清。准备阶段给静态变量分配内存并设置默认零值,初始化阶段才执行静态代码块和变量赋值的真正逻辑,这个顺序不能搞反。
双亲委派模型:启动类加载器、扩展类加载器、应用程序加载器,一层层往上请示,没有找到才往下加载。好处是避免核心API被篡改。但面试官会追问:“如果我想让一个类加载两遍,或者实现热部署,怎么办?”这时需要打破双亲委派。Tomcat的WebAppClassLoader就是一个反例,它没有完全遵循双亲委派,而是先自己加载Web应用下的类,再交给父加载器。热部署的本质是同名类的重新加载,加载器不同,类就被视为两个不同的类。这个知识点能反映出你是否理解“全限定名+类加载器”共同唯一标识一个类。
synchronized的锁升级,才是并发的基础
并发模块的第一座山是synchronized。不要再说“synchronized是悲观锁”这么简单的话了。JDK后的锁升级路径:无锁→偏向锁→轻量级锁→重量级锁。偏向锁是给“只有一个线程访问”的局部变量准备的,轻量级锁是给“多个线程交替访问”准备的,重量级锁才是真正的互斥。synchronized在字节码层面靠monitorenter和monitorexit实现,每个对象头里都有Mark Word,锁状态就记录在Mark Word里。
面试官常问:锁升级是单向的吗?答案不是。偏向锁在竞争时可以升级为轻量级锁,但在JDK15之后偏向锁被废弃,所以你应该关注的是“锁消除”和“锁粗化”这两个编译器优化。锁消除发生在JIT判断对象不会被共享时,它会直接把synchronized代码块中的锁去掉。如果你能举出StringBuffer被局部变量引用的例子,面试官会认为你不仅了解锁,还了解现代编译器的优化手段。
volatile绝不只是“可见性”三个字
volatile是并发进阶的一个分水岭。网上资料总说“volatile保证可见性,不保证原子性”,但面试官更愿意问:“volatile是怎么保证可见性的?”答案是指令重排序和内存屏障。volatile写操作会在前面插入StoreStore屏障,后面插入StoreLoad屏障;volatile读操作会在后面插入LoadLoad和LoadStore屏障。这些屏障防止了CPU指令乱序执行。
还有一个高频场景:双重检查锁创建单例。为什么那个instance要声明为volatile?因为new对象不是原子操作,要先分配内存、初始化对象、将引用指向内存。如果被重排成1-3-2,另一个线程就会拿到一个“未初始化”的对象。volatile禁止了这个重排序,保证返回的一定是完整对象。面试到这里,你可以把JMM的happens-before规则也带出来:写volatile变量前的内容,对后续读这个volatile的线程都是可见的。这比单纯背规则更有说服力。
线程池的参数,面试官要的是你的取舍
最后一个高频考点是线程池。核心线程数、最大线程数、阻塞队列容量、线程空闲存活时间、拒绝策略,这五个参数直接决定性能。面试官不会满足于你背出threadPoolExecutor的构造方法,他会问:“核心线程数和最大线程数,在CPU密集型和IO密集型任务中分别怎么设置?”CPU密集型用N+1,IO密集型用2N,这只是经验值,真正的决定性因素是任务阻塞时间与CPU执行时间的比例。
线程池的工作流程要描述清楚:提交任务时先判断核心线程是否满,没满就创建线程执行;满则进入队列;队列满则创建非核心线程;达到最大线程数就走拒绝策略。拒绝策略不是只有抛出异常一种,还有DiscardPolicy、DiscardOldestPolicy和CallerRunsPolicy,后者让提交线程自己执行任务,可以减缓提交速度。
面试官最喜欢挖的坑是:一个线程池线程数为5,最大为10,队列容量为100,提交20个任务会创建几个线程?答案是5个线程运行5个任务,剩下15个进入队列,不会创建新线程。很多人把队列当成兜底,其实队列是线程池缓冲的第一道防线。如果你能主动提到线程池线程的生命周期、如何优雅关闭、如何用ThreadFactory指定线程名,面试官会在心里给你加一分。而真正让他记住你的,是你对“合理配置线程池”有自己的方法论——比如通过压测观察TP99,动态调整参数。
这8个知识点,从HashMap到线程池,从内存模型到锁升级,本质上是一条“从单机到并发”的完整链路。面试官不是要你背答案,而是要你看清每个技术背后的设计困境和权衡。HashMap在并发下的脆弱,ConcurrentHashMap的精细控制,JVM区域划分的代价,GC的停顿权衡,类加载的隔离需求,synchronized的渐进升级,volatile的屏障语义,线程池的资源取舍——每个点都是一场“在约束条件下做优化”的思想实验。下次面试时,当你能够讲出背后的取舍,而不仅仅是结论,你就已经赢了那些只会背八股的人。