2026.4.28 d6
1. HashMap 的底层实现原理
JDK 1.8 之后:
- 数据结构:数组 + 链表 + 红黑树
- 数组:Node<K,V>[] table
- 每个位置叫 桶(bucket)
流程:
- key → hash
- hash → (n - 1) & hash 定位数组下标
- 发生冲突:
- 链表(长度 < 8)
- 红黑树(长度 ≥ 8 且数组长度 ≥ 64) 关键点:
- 负载因子(loadFactor)默认 0.75
- 扩容:容量 × 2
- 链表转红黑树阈值:8
- 红黑树转链表阈值:6
2. put(k, v) 的流程
put(key, value)
↓
计算 hash(key)
↓
index = (n - 1) & hash
↓
判断 table[index] 是否为空
↓
为空 → 直接插入
↓
不为空
↓
判断是否 key 相同
↓
相同 → 覆盖 value
↓
不同 → 插入链表 / 红黑树
↓
链表长度 ≥ 8 → 转红黑树
↓
size++
↓
是否超过阈值 threshold
↓
超过 → resize()
和 HashMap 的线程安全集合
ConcurrentHashMap
- JDK1.7:Segment 分段锁
- JDK1.8:CAS + synchronized,桶为空使用CAS操作,不为空加锁操作 Hashtable
- 方法级 synchronized
- 粗粒度锁,性能差 Collections.synchronizedMap
- 本质:包装 HashMap
- 整体加锁
AQS和ReentrantLock
AQS:Java 并发包里的一个同步器框架。 AQS = state 状态变量 + CLH 等待队列 + CAS 修改状态
AQS
├── state:同步状态
│ └── int 类型,表示锁是否被占用、资源数量等
│
├── CAS:修改 state
│ └── 保证并发安全
│
└── 等待队列
└── 获取锁失败的线程进入队列等待
线程获取锁时,会先通过 CAS 修改 state,如果失败就进入队列排队,并通过 LockSupport.park 阻塞;释放锁时通过 unpark 唤醒后继节点。
AQS 使用模板方法模式,将 tryAcquire / tryRelease 等方法交给子类实现,从而支持 ReentrantLock、Semaphore、CountDownLatch 等不同同步器。
synchronized 锁升级流程
锁状态(从低到高)
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
升级过程
- 无锁
- 对象刚创建,没有竞争
- 偏向锁
- 第一个线程进入,记录线程ID到对象头
- 后续该线程进入不需要加锁(无CAS)
- 适用于“单线程反复进入”
- 轻量级锁
- 多线程竞争,但不激烈
- 通过 CAS + 自旋 尝试获取锁
- 避免线程阻塞
- 重量级锁
- 自旋失败 or 竞争激烈
- 升级为 OS 互斥锁(mutex)
- 线程阻塞 + 上下文切换
- 锁 只能升级,不能降级
- 目的:减少用户态 → 内核态切换
MySQL 优化方向
1. SQL 层
- 避免
select * - 避免函数、计算导致索引失效
- 使用覆盖索引
2. 索引层
- 合理设计 联合索引(最左匹配)
- 避免索引失效(隐式类型转换等)
3. 架构层
- 读写分离
- 分库分表
- 缓存(Redis)
分库分表
垂直拆分(Vertical)
按“字段 ”拆
例:
user表 → user_basic + user_detail
特点:
- 减少单表字段数
- 提高缓存命中率
水平拆分(Horizontal)
👉 按“数据行”拆
例:
user_0, user_1, user_2 (按 user_id % 3)
特点:
- 解决数据量过大
- 提高并发
三、MySQL 事务隔离级别
四种隔离级别
| 隔离级别 | 能解决的问题 |
|---|---|
| 读未提交(RU) | 无 |
| 读已提交(RC) | 脏读 |
| 可重复读(RR) | 脏读 + 不可重复读 |
| 串行化(Serializable) | 脏读 + 不可重复读 + 幻读 |
- 脏读:读到未提交数据
- 不可重复读:查询同一行数据多次前后不一致
- 幻读:相同的范围查询结果
Redis 持久化机制
RDB(快照)
- 定时生成 dump.rdb
- fork 子进程 + COW(写时复制) 优点:
- 文件小,恢复快 缺点:
- 数据可能丢失
AOF(日志)
- 记录每次写操作 策略:
- always(每次刷盘)
- everysec(默认)
- no(操作系统决定) 优点:
- 数据更安全 缺点:
- 文件大
混合持久化
- RDB + AOF Redis 混合持久化是在 AOF rewrite 时,先生成 RDB 快照作为基础数据,再追加 AOF 增量日志。
恢复时先加载 RDB,再重放 AOF,兼顾恢复速度和数据安全性。
Redis 内存淘汰策略
不淘汰
- noeviction
TTL相关
- volatile-lru
- volatile-lfu
- volatile-random
- volatile-ttl
全局淘汰
- allkeys-lru
- allkeys-lfu
- allkeys-random
六、JVM 内存结构
JVM
├── 堆(Heap)
│ ├── 新生代(Eden + S0 + S1)
│ └── 老年代
├── 方法区(MetaSpace)
├── 虚拟机栈(Java Stack)
├── 本地方法栈
└── 程序计数器
各区域作用
- 堆
- 存对象
- GC主要区域
- 方法区(MetaSpace)
- 类信息、常量
- 虚拟机栈
- 方法调用、局部变量
- 程序计数器
- 记录当前执行字节码文件位置,方便线程切换后找到执行位置
常见垃圾回收算法
标记-清除
- 标记:标记所有存活对象
- 清除:回收未标记对象
复制算法
- 将内存分为两块(from / to,年轻代的S0和S1)
- 存活对象复制到另一块
- 清空原区域
标记-整理
- 标记存活对象
- 将存活对象向一端移动
- 清理边界外内存
常见收集器
- Serial
- ParNew
- CMS
- G1 把堆切成大小相等的 Region,每个 Region 在运行时动态充当 Eden / Survivor / Old之一,GC 优先回收性价比最高(性价比 = 可释放空间 / 预估回收耗时)的 Region,兼顾吞吐与延迟。
- ZGC
用着色指针在对象引用里存元数据,配合读屏障,让几乎所有 GC 工作都与应用并发执行,STW 只剩根扫描的极短暂停。
ZGC着色指针这样存:
reference → [状态位 | 对象地址]
传统GC时,移动对象时所有引用都需要更新,着色指针的好处是把对象的状态信息编码进引用中,使得 JVM 在访问对象时可以通过读屏障判断引用是否需要修正。
应用线程读取对象引用
↓
触发读屏障
↓
检查指针上的“颜色”
↓
判断对象是否已移动
↓
如果移动 → 修正为新地址
↓
继续执行
面经链接
每日面经记录 文章被收录于专栏
记录每天Java和Agent面经学习
查看6道真题和解析