JVM
内存结构 · 垃圾回收 · 类加载 · 排查
运行时内存区域
- 线程私有:程序计数器、虚拟机栈、本地方法栈
- 线程共享:堆(对象实例)、方法区(类元信息、运行时常量池)
- JDK 8 之后方法区由元空间实现,位于本地内存,受
-XX:MaxMetaspaceSize约束 - 字符串常量池在 JDK 7 就移到了堆里
垃圾回收
- 判定存活用可达性分析,不是引用计数(引用计数解决不了循环引用)
- GC Roots:栈帧中的局部变量、静态变量、常量、JNI 引用、活跃线程
- 分代假设:绝大多数对象朝生夕死 → 新生代用复制算法,老年代用标记-整理
- 对象进入老年代:熬过 15 次 Minor GC(
-XX:MaxTenuringThreshold)、大对象直接进、动态年龄判定 - G1 把堆切成等大 Region,按回收价值排序优先回收,可预测停顿
面试常问:为什么 Full GC 要 stop the world? 标记阶段需要保证对象引用关系不变,否则会误判存活/已死,所以必须暂停所有应用线程。
类加载
- 过程:加载 → 验证 → 准备 → 解析 → 初始化
- 双亲委派:先交给父加载器,父加载不了才自己加载 —— 保证核心类不被篡改
- 打破双亲委派的场景:SPI(JDBC 驱动)、Tomcat 的 WebApp 隔离、OSGi
- 准备阶段赋零值,初始化阶段才执行
<clinit>赋真正的值
线上排查
jps -l # 找进程
jstat -gcutil <pid> 1000 # GC 频率与停顿时长
jmap -dump:live,format=b,file=heap.hprof <pid>
jstack <pid> # 线程栈,查死锁
# 线上更常用 arthas / async-profiler,因为 dump 会 STW
集合
HashMap · ConcurrentHashMap · List
HashMap
- 数组 + 链表 + 红黑树(链表长度 ≥ 8 且数组长度 ≥ 64 时转树)
- 默认容量 16,负载因子 0.75,扩容翻倍
- 下标计算:
(n - 1) & hash,所以容量必须是 2 的幂 - hash 扰动:
h ^ (h >>> 16),让高位也参与运算,减少碰撞 - JDK 8 尾插法(解决成环),JDK 7 是头插
ConcurrentHashMap
- JDK 7:分段锁 Segment,锁粒度是段
- JDK 8:
CAS + synchronized,锁粒度细化到桶的第一个节点 - get 不加锁,靠 volatile 保证可见性
- size() 用 baseCount + CounterCell 累加,避免热点
Collections.synchronizedMap 是整表一把锁,并发度远低于 ConcurrentHashMap。
ArrayList vs LinkedList
- ArrayList 底层数组,随机访问 O(1),中间插入 O(n)
- LinkedList 双向链表,插入删除 O(1),但随机访问 O(n),且每个节点额外开销大
- 实际项目里 ArrayList 几乎总是更优,LinkedList 只在头尾频繁操作且不访问时才有优势
- ArrayList 扩容 1.5 倍(
old + (old >> 1))
并发
线程池 · 锁 · AQS · 内存模型
线程池
- 七参数:核心线程数、最大线程数、空闲存活时间、时间单位、工作队列、线程工厂、拒绝策略
- 执行顺序:核心线程 → 队列 → 非核心线程 → 拒绝策略
- 拒绝策略四种:AbortPolicy(抛异常)、CallerRunsPolicy(调用线程执行)、DiscardPolicy、DiscardOldestPolicy
- 禁止用
Executors创建,因为 newFixedThreadPool 用无界队列会 OOM,newCachedThreadPool 线程数无上限
volatile 与 synchronized
- volatile 保证可见性和有序性,不保证原子性
- 底层靠内存屏障:写前 StoreStore,写后 StoreLoad
- synchronized 锁升级:无锁 → 偏向锁 → 轻量级锁(CAS 自旋)→ 重量级锁(monitor)
CAS 与 ABA
- CAS 是 CPU 指令级别,无锁但会自旋消耗 CPU
- ABA 问题用版本号解决,即
AtomicStampedReference
AQS
- 核心:一个
volatile state+ 一个 CLH 双向队列 - 独占模式用
tryAcquire / tryRelease,共享模式用tryAcquireShared - ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock 都基于 AQS
MySQL
索引 · 事务 · MVCC · 优化
索引
- InnoDB 用 B+ 树:非叶子只存键,叶子存数据且用双向链表相连 —— 树矮、范围查询快
- 聚簇索引:主键即数据,二级索引叶子存的是主键值,所以要回表
- 覆盖索引:查询字段全在索引里就不用回表
- 最左前缀:联合索引 (a,b,c) 能走 a、a+b、a+b+c,走不了 b、c
- 索引失效:函数运算、隐式类型转换、
like '%x'、or两侧有非索引列、范围查询后的列
事务与隔离级别
- 四种隔离级别:读未提交、读已提交、可重复读(默认)、串行化
- RC 解决脏读,RR 解决不可重复读,串行化解决幻读
- InnoDB 的 RR 用间隙锁在很大程度避免了幻读
- MVCC:隐藏字段 + undo log 版本链 + ReadView
- RC 每次快照读都新建 ReadView;RR 只在第一次快照读时创建
锁
- 表锁、行锁、间隙锁、临键锁(Next-Key Lock = 行锁 + 间隙锁)
- 行锁加在索引上,没有索引会退化成表锁
- 死锁排查:
SHOW ENGINE INNODB STATUS
慢查询
EXPLAIN SELECT ...;
-- 重点看
-- type: system > const > eq_ref > ref > range > index > ALL
-- key: 实际用到的索引
-- rows: 预估扫描行数
-- Extra: Using index(覆盖) / Using filesort(要排序) / Using temporary(要临时表)
Redis
缓存问题 · 持久化 · 分布式锁
缓存三兄弟
- 穿透:查不存在的数据,请求全打到库 → 布隆过滤器 / 缓存空值
- 击穿:热点 key 过期瞬间大量请求 → 互斥锁重建 / 逻辑过期
- 雪崩:大量 key 同时失效 → 过期时间加随机值 / 多级缓存 / 限流降级
持久化
- RDB:快照,恢复快、体积小,但可能丢数据
- AOF:记命令,
always / everysec / no三档刷盘策略 - 混合持久化(4.0+):RDB 做基准 + AOF 记增量
分布式锁
SET lock_key token NX PX 30000
-- 释放必须用 Lua 保证原子性:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else return 0 end
Redis 主从异步复制,主节点宕机可能丢锁。对一致性要求高的场景用 Redlock 或换 ZooKeeper / etcd。
其他高频
- 单线程指命令执行单线程,网络 IO 在 6.0 之后是多线程
- 大 key 危害:阻塞、网络拥塞、删除卡顿
- 过期策略:惰性删除 + 定期删除;内存淘汰 8 种策略,常用 allkeys-lru
Spring
IoC · AOP · 循环依赖 · 事务
IoC 与循环依赖
- 三级缓存:
singletonObjects、earlySingletonObjects、singletonFactories - 二级能解决普通循环依赖,三级是为了AOP 代理——提前暴露的是工厂而不是实例
- 循环依赖只能解决单例 + setter/字段注入;构造器注入和 prototype 解决不了
AOP
- 动态代理:JDK(接口)或 CGLIB(子类)
- Spring Boot 2.x 起默认 CGLIB
事务失效的常见场景
- 方法不是
public - 同类内部调用——绕过了代理,这是最高频的坑
- 异常被 catch 没抛出,或抛的是检查异常(默认只回滚 RuntimeException)
- 类没被 Spring 管理
- 数据库引擎不支持事务(MyISAM)
内部调用要生效,可以注入自己、用
AopContext.currentProxy(),或者干脆把方法拆到另一个 Service。
微服务
注册中心 · 幂等 · 限流 · 分布式事务
Nacos
- 单机模式默认内嵌 Derby,集群默认外置 MySQL
- 2.x 起客户端走 gRPC,端口是主端口 + 1000(8848 → 9848)
- 3.x 把控制台拆出去,独立端口 8080
幂等
- 唯一索引(最可靠)、token 机制、状态机、分布式锁
- 关键:幂等键要能覆盖真实的重复请求,而不是简单按参数哈希
限流
- 固定窗口、滑动窗口、漏桶、令牌桶
- 单机用 Guava RateLimiter(令牌桶),分布式用 Redis + Lua
- Sentinel 支持流控、熔断降级、热点参数限流
分布式事务
- 2PC / XA:强一致,性能差
- TCC:业务侵入大,要做三个方法
- 本地消息表 / 事务消息:最终一致,工程上最常用
- Saga:长事务补偿,适合流程型业务
系统设计
短链 · 秒杀 · 排行榜
短链服务
- 发号:号段模式或雪花,避免每次查库
- 编码:
62 进制把数字转字符串,6 位可支撑 568 亿 - 存储:MySQL 存映射 + Redis 缓存热点
- 跳转用 302 而非 301,才能统计点击
- 统计异步化:写消息队列再落库,别拖慢跳转
秒杀
- 层层过滤:前端限流 → 网关限流 → Redis 预扣库存 → 消息队列削峰 → 异步落库
- 库存扣减用 Lua 保证原子性
- 超卖靠数据库乐观锁兜底:
UPDATE ... SET stock = stock - 1 WHERE id = ? AND stock > 0
排行榜
- Redis
ZSET,ZADD更新、ZREVRANGE取 Top N、ZREVRANK查排名 - 超大规模要分片 ZSET 再归并
外部资料
别人的原创内容,直接去原站看 · 不转载、不镜像
以上站点内容版权归各自作者所有。此处仅作链接导航,未转载或镜像任何正文。