从JVM到并发编程的核心剖析
目录导读
- JVM底层原理篇:内存模型、垃圾回收与类加载机制
- 并发编程底层篇:synchronized、volatile与AQS源码解析
- 集合框架底层篇:HashMap、ConcurrentHashMap的红黑树与扩容
- 网络与IO底层篇:BIO/NIO/AIO与Reactor模式
- 常见面试问答集锦:高频问题深度解答
JVM底层原理篇
JVM内存模型与对象创建过程
面试官常问:“一个对象从加载到使用,JVM内部发生了什么?” 核心在于理解类加载机制与对象内存布局,类加载通过双亲委派模型,从加载、验证、准备、解析到初始化,对象创建则需经过:检查类是否加载、分配内存(指针碰撞或空闲列表)、初始化零值、设置对象头、执行init方法。
问答环节:
- 问:对象头包含哪些信息?
答:Mark Word(存储锁状态、GC分代年龄、hashCode等)、类型指针(指向类元数据)、数组长度(如果是数组)。
垃圾回收算法与CMS/G1调优
面试必问:可达性分析的GC Roots有哪些?三色标记如何解决漏标?CMS的并发标记阶段如何工作?G1的Region与停顿预测模型如何实现可控停顿?
核心点:
- 新生代:复制算法(Minor GC)
- 老年代:标记-清除/标记-整理(CMS、G1)
- 调优参数:-Xms、-Xmx、-XX:NewRatio、-XX:+UseG1GC等
问答环节:
- 问:为什么CMS会出现Concurrent Mode Failure?
答:当老年代空间不足,且后台线程回收速度跟不上分配速度时,会触发Serial Old进行单线程Full GC。
并发编程底层篇
synchronized的锁升级过程
从无锁 → 偏向锁 → 轻量级锁 → 重量级锁,每个阶段的触发条件与Mark Word变化是高频考点,Java 6后通过锁消除和锁粗化优化性能。
核心源码:偏向锁通过CAS在对象头存储线程ID;轻量级锁通过自旋;重量级锁依赖OS互斥量(Mutex)。
问答环节:
- 问:偏向锁为什么在JDK 15被默认禁用?
答:因高并发场景下撤销偏向锁的成本高,且现代应用多使用轻量级锁,默认禁用可减少JIT优化复杂度。
volatile与JMM的happens-before
volatile保证可见性(通过Lock前缀指令刷新缓存)和有序性(禁止指令重排序),但不保证原子性,面试常结合双重检查锁(DCL) 问为什么需要volatile。
问答环节:
- 问:volatile能替代synchronized吗?
答:不能,volatile只解决读写可见性和有序性,但不保证复合操作(如i++)的原子性。
AQS的实现原理与ReentrantLock
AQS(AbstractQueuedSynchronizer)通过CLH队列(FIFO双向链表)和状态变量state实现同步,ReentrantLock的公平/非公平体现在tryAcquire中:非公平直接CAS;公平检查队列是否有前任。
核心源码:
tryAcquire:CAS设置state,exclusiveOwnerThreadacquireQueued:线程在队列中自旋或park
问答环节:
- 问:CountDownLatch与CyclicBarrier底层实现有何不同?
答:CountDownLatch基于AQS的共享模式,state为计数器;CyclicBarrier基于ReentrantLock和Condition,支持循环使用。
集合框架底层篇
HashMap的put与get源码解析
8后采用数组+链表+红黑树,扩容机制:默认0.75负载因子,达到阈值时resize(2倍扩容),链表长度>8且数组长度≥64转红黑树。
问答环节:
- 问:为什么HashMap的key需重写hashCode和equals?
答:hashCode决定桶位置,equals判断键是否相同;避免哈希冲突导致覆盖或查询失败。
ConcurrentHashMap的线程安全实现
7使用分段锁(Segment数组),8使用CAS+synchronized(对每个Node桶加锁),扩容时通过transfer方法让多线程并行迁移。
问答环节:
- 问:ConcurrentHashMap的size()方法如何统计?
答:通过CounterCell数组累加,使用LongAdder思想减少CAS竞争。
网络与IO底层篇
BIO/NIO/AIO的区别与Reactor模型
BIO阻塞模式,NIO通过Selector实现多路复用,AIO基于回调/通知。Reactor模型(单Reactor单线程、多线程、主从多线程)是Netty核心,通过事件驱动处理连接和IO。
问答环节:
- 问:NIO的epoll为什么比select高效?
答:select需遍历全量fd并每次拷贝到内核;epoll通过红黑树注册事件,只返回就绪fd,且使用mmap减少拷贝。
常见面试问答集锦(深度Q&A)
Q1:JVM年轻代为什么分为Eden和两个Survivor区?
答:基于分代收集和标记-复制算法,Eden存放新对象,存活对象复制到S0/S1,默认比例8:1:1,减少内存碎片,提升Minor GC效率。
Q2:ThreadLocal的内存泄漏如何产生?如何避免?
答:ThreadLocalMap的Key是弱引用,但Value是强引用,若ThreadLocal被GC回收,Entry存在Key=null但Value有效,导致内存泄漏。避免:每次使用后调用remove();或使用静态内部类封装。
Q3:Redis与MySQL双写一致性为什么要用延时双删?
答:先更新数据库,再删除缓存,但并发下存在旧数据读入缓存风险,延时双删(先删缓存,再删一次)通过短暂延迟保证最终一致,且配合缓存过期时间兜底。
Q4:分布式锁用Redis还是Zookeeper?底层差异?
答:Redis基于SETNX+过期时间(需原子性),存在主从切换锁丢失风险;Zookeeper基于临时顺序节点+监听,强一致性但性能略低,选择取决于业务对一致性与性能的要求。
底层源码面试不仅考察记忆,更考验原理理解与场景联想,建议从JVM、并发、集合三大核心模块入手,结合源码片段(如HashMap的红黑树平衡、AQS的CLH队列入队)进行思维导图整理,面试时,回答前先问清场景,然后分层解析:为什么这样设计 → 解决了哪些问题 → 遇到过什么坑,掌握这些,面试官自然会对你的底层功底刮目相看。
如果您希望针对特定题目(如“怎么设计一个MQ”或“Redis持久化原理”)展开深度解析,欢迎留言讨论。
标签: 垃圾回收机制