2026.4.28 d6

1. HashMap 的底层实现原理

JDK 1.8 之后:

  • 数据结构:数组 + 链表 + 红黑树
  • 数组:Node<K,V>[] table
  • 每个位置叫 桶(bucket)

流程:

  1. key → hash
  2. hash → (n - 1) & hash 定位数组下标
  3. 发生冲突:
    • 链表(长度 < 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 锁升级流程

锁状态(从低到高)

无锁 → 偏向锁 → 轻量级锁 → 重量级锁

升级过程

  1. 无锁
    • 对象刚创建,没有竞争
  2. 偏向锁
    • 第一个线程进入,记录线程ID到对象头
    • 后续该线程进入不需要加锁(无CAS)
    • 适用于“单线程反复进入”
  3. 轻量级锁
    • 多线程竞争,但不激烈
    • 通过 CAS + 自旋 尝试获取锁
    • 避免线程阻塞
  4. 重量级锁
    • 自旋失败 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)
    • 类信息、常量
  • 虚拟机栈
    • 方法调用、局部变量
  • 程序计数器
    • 记录当前执行字节码文件位置,方便线程切换后找到执行位置

常见垃圾回收算法

标记-清除

  1. 标记:标记所有存活对象
  2. 清除:回收未标记对象

复制算法

  1. 将内存分为两块(from / to,年轻代的S0和S1)
  2. 存活对象复制到另一块
  3. 清空原区域

标记-整理

  1. 标记存活对象
  2. 将存活对象向一端移动
  3. 清理边界外内存

常见收集器

  • Serial
  • ParNew
  • CMS
  • G1 把堆切成大小相等的 Region,每个 Region 在运行时动态充当 Eden / Survivor / Old之一,GC 优先回收性价比最高(性价比 = 可释放空间 / 预估回收耗时)的 Region,兼顾吞吐与延迟。
  • ZGC 用着色指针在对象引用里存元数据,配合读屏障,让几乎所有 GC 工作都与应用并发执行,STW 只剩根扫描的极短暂停。 ZGC着色指针这样存:reference → [状态位 | 对象地址]

传统GC时,移动对象时所有引用都需要更新,着色指针的好处是把对象的状态信息编码进引用中,使得 JVM 在访问对象时可以通过读屏障判断引用是否需要修正。

应用线程读取对象引用
        ↓
触发读屏障
        ↓
检查指针上的“颜色”
        ↓
判断对象是否已移动
        ↓
如果移动 → 修正为新地址
        ↓
继续执行

alt

面经链接

熙牛-电话一面 2026.4.17_牛客网

每日面经记录 文章被收录于专栏

记录每天Java和Agent面经学习

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务