快手 Java 二面面经(9.17)
- 轮次
- 二面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 手撕 LRU
- bean 的生命周期
- AOP 解释,怎么实现 AOP
- 线程池拒绝策略
- hashmap 为什么不安全,后面怎么保证安全
- 不安全的 hashmap 扩容的时候为什么会死锁
- volatile 原理,为什么不能保证原子性,怎么保证
- 垃圾回收机制,标记计数法的原理
- 线程和协程有什么区别,协程原理是什么
- mysql 索引数据结构,为什么选这个,有没有不用 B+ 树的
- redis 写硬盘机制,AOF 详细解释下
《参考解析》
- LRU 手撕:标准写法是
HashMap + 双向链表。HashMap存 key 到节点的映射,双向链表头部放最近使用、尾部放最久未用;get命中后把节点摘下来插到头部,put时若容量已满就删尾节点再插头部。要一次通过得注意三点:用带虚拟头尾的哨兵节点省掉边界判断、put已存在的 key 时要更新值并刷新位置、get未命中返回 -1。Java 里LinkedHashMap(capacity, 0.75f, true)加removeEldestEntry十几行就能实现,面试可以先说这个再手写完整版。 - Bean 的生命周期:实例化 → 属性填充 →
Aware系列回调(BeanNameAware/BeanFactoryAware/ApplicationContextAware)→BeanPostProcessor.postProcessBeforeInitialization→@PostConstruct/InitializingBean.afterPropertiesSet/ 自定义 init-method →BeanPostProcessor.postProcessAfterInitialization(AOP 代理基本在这一步生成)→ 使用 → 容器关闭时@PreDestroy/DisposableBean.destroy。 - 为什么需要 AOP 代理:AOP 靠动态代理把横切逻辑织入目标方法调用链。JDK 动态代理基于接口,CGLIB 通过继承生成子类;Spring Boot 2.x 起默认
proxyTargetClass=true走 CGLIB。理解这一点能顺带解释一连串现象——为什么同类自调用事务失效、为什么final方法切不进去、为什么注入的是代理对象而不是原始类型。 - HashMap 扩容死锁:JDK 1.7 的头插法在并发扩容时,两个线程同时把链表节点往新表搬,会让链表形成环,之后
get落到这个桶上就死循环把 CPU 打满。JDK 1.8 改成尾插法消除了成环,但并发put仍可能丢数据、size 计数不准,所以它依然不是线程安全的。要并发就用ConcurrentHashMap(1.8 是 CAS + 桶头 synchronized),或者给操作加锁。 - volatile 为什么不能保证原子性:volatile 通过内存屏障保证可见性和禁止指令重排,但
count++是「读 - 改 - 写」三步复合操作,两个线程可能都读到 1 再各自写回 2。要保证原子性可以用synchronized、ReentrantLock或AtomicInteger(CAS + 自旋,失败重试)。volatile 只适合「一个线程写、多个线程读」的状态标记场景。 - 引用计数法与可达性分析:引用计数给每个对象维护被引用次数,为 0 就回收,问题是无法处理循环引用(A 引用 B、B 引用 A 时计数永远不为 0)。所以 JVM 实际用的是可达性分析——从 GC Roots(栈帧局部变量、静态变量、常量、JNI 引用等)出发做遍历,走不到的对象判为可回收。Python 用的是引用计数 + 分代标记清除来补环。
- MySQL 为什么选 B+ 树:关键在磁盘 IO 次数和范围查询。B+ 树非叶子节点只存键,扇出大、树高低,定位一个叶子节点的 IO 次数少;叶子节点用双向链表串联,范围扫描和排序都不用回上层。B 树的数据分散在所有节点上,范围查询需要中序遍历,IO 更碎;哈希索引等值查询快但不支持范围;跳表 / LSM 树(如 RocksDB)在写密集场景更合适,MySQL 的 InnoDB 没有采用。
- Redis 的 AOF:把每条写命令追加到日志文件,重启时重放恢复。流程是「命令写入 aof_buf →
write到内核缓冲区 →fsync刷盘」,appendfsync三档决定安全性与性能的取舍:always每条都刷、最安全但最慢;everysec(默认)每秒刷一次,最多丢 1 秒数据;no交给操作系统。AOF 文件会随重放不断膨胀,所以有重写机制(BGREWRITEAOF)生成最小命令集,重写期间新命令写进重写缓冲区,完成后追加进新文件并替换。Redis 4.0 之后推荐 AOF + RDB 混合持久化。