2026.4.25 面经学习第三天
雪花算法
主要用于生成全局唯一ID,并且长用来作为数据表主键 雪花算法=符号位+时间戳+机械码+序列号 优点是性能高,不依赖数据库,天然支持分布式 缺点是会有时钟回拨产生重复ID和序列号溢出的风险
时钟回拨问题怎么解决?
检测并阻塞
当检测到当前时间 < 上一次时间时,直接拒绝或阻塞,等待时间追平。这里的等待时间追平是指系统时间追上一次生成ID的时间
多时钟序列
引入“时钟版本号”(clockId),时间回拨时切换 clockId 保证不同时间段生成的 ID 不冲突(类似逻辑分片)
序列号溢出怎么解决
等待下一毫秒
当序列号用完,阻塞到下一毫秒再继续生成这是默认实现,简单且可靠
扩展序列号位数
增加序列号 bit 数(例如从 12 bit → 14 bit代价是会压缩时间戳或机器位
为什么选择RabbitMQ
选择 RabbitMQ,不是因为它一定比 Kafka 更强,而是因为我的场景更偏业务异步任务,需要可靠投递、消费确认、失败重试和死信队列。RabbitMQ 功能足够成熟,接入成本也比较低,所以更合适。
定时调度任务
定时调度任务主要用于按固定时间或固定周期自动执行后台逻辑。
Spring的定时任务
先在启动类上开启定时任务
@EnableScheduling
@SpringBootApplication
public class Application {
}
然后在方法上加 @Scheduled:
@Component
public class TaskJob {
@Scheduled(cron = "0 0/5 * * * ?")
public void run() {
System.out.println("每 5 分钟执行一次");
}
}
@Scheduled的参数
@Scheduled(fixedRate = 5000) 从任务开始时间算,每隔 5 秒触发一次。 @Scheduled(fixedDelay = 5000) 从任务结束时间算,任务执行完后,再等 5 秒触发下一次。 @Scheduled(initialDelay = 3000, fixedRate = 5000) 项目启动后先等 3 秒,再每隔 5 秒执行一次。 @Scheduled(cron = "0 0/5 * * * ?") 使用 cron 表达式,这里表示每 5 分钟执行一次。 @Scheduled(zone = "Asia/Shanghai", cron = "0 0 2 * * ?")
Redis的淘汰策略
noeviction
不淘汰,直接报错(写失败)
volatile 系列(只淘汰设置了过期时间的 key)
- volatile-lru:淘汰最近最少使用
- volatile-lfu:淘汰使用频率最低
- volatile-random:随机淘汰
- volatile-ttl:淘汰即将过期的
allkeys 系列(淘汰所有 key)
- allkeys-lru:最常用(推荐)
- allkeys-lfu
- allkeys-random lru 最近最少使用,lfu最不常用
多租户设计
多租户设计就是一套系统支持多个租户使用,并保证租户之间的数据隔离。常见方案有独立数据库、独立 Schema、共享数据库共享表。独立数据库隔离性最好但成本高,共享表成本最低但要重点防止数据串租。实际项目中常用共享表方案,在业务表中增加 tenant_id,登录后从 Token 中解析 tenant_id 放入上下文,通过 MyBatis 拦截器或 MyBatis-Plus 多租户插件自动给 SQL 拼接 tenant_id 条件,同时结合权限控制、联合索引和数据校验来保证安全和性能。
RAG的流程
Java的内存模型
JMM是一套并发编程规范,核心是解决并发场景下共享变量的可见性、有序性和原子性问题。
可见性
一个线程对变量的修改,其他线程是否能立即看到 实现:
volatile:
写操作会刷新到主内存,读操作会从主内存读取synchronized:
释放锁前会把工作内存刷新到主内存,获取锁会重新读取 注意:volatile只保证有序性和可见性,不保证原子性 对于volatile修饰的变量x,x++、++x、x+=1都不是原子操作
有序性
代码执行顺序是否符合预期
- happens-before 规则(核心)
volatile:禁止指令重排(部分)synchronized:通过内存屏障保证顺序
原子性
操作是否不可被中断
synchronized:保证临界区原子性Lock(如 ReentrantLock)CAS(Atomic 类,如 AtomicInteger)
volatile的实现原理
可见性:
1)JVM 会在写操作后插入写屏障
2)强制把 CPU 缓存中的数据刷新到主内存
读 volatile 变量时:
1)JVM 会在读操作前插入读屏障
2)使 CPU 缓存失效,从主内存重新加载
有序性:
volatile 会禁止特定的指令重排:
- 写 volatile 之前的操作不能排到它后面
- 读 volatile 之后的操作不能排到它前面
happens-before常见规则
1)程序顺序规则
同一个线程内,代码按顺序执行
前面的操作 happens-before 后面的操作
2)volatile 变量规则
对一个 volatile 变量的写
happens-before
后续对该变量的读
3)锁规则(monitor)
解锁(unlock)
happens-before
后续加锁(lock)
4)传递性(重要)
如果:
A happens-before B
B happens-before C
那么:
A happens-before C
栈溢出的情况
- 递归没有终止条件
- 递归层级过深
- 栈帧过大
对象创建的步骤
1)类加载检查
当执行 new 时,先检查类是否已被加载
如果没有,会触发类加载(加载 → 验证 → 准备 → 解析 → 初始化)
2)分配内存
在堆中为对象分配内存 方式有两种:
- 指针碰撞(堆内存规整)
- 空闲列表(堆内存不规整)
3)初始化零值
把分配的内存初始化为默认值(0、null、false)
4)设置对象头
包括:
- Mark Word(哈希、锁信息、GC 年龄等)
- 类型指针(指向类元数据)
5)执行构造方法(init)
执行 <init> 方法,按代码逻辑赋值
6)引用赋值(关键)
把对象地址赋值给引用变量
注意
指令重排问题
对象创建不是完全有序的,可能发生:
1)分配内存
2)初始化对象
3)引用指向内存
可能被重排为:
1)分配内存
3)引用指向内存
2)初始化对象
如何判断对象已经死亡
- 引用计数器:对象每被引用一次 +1,引用失效 -1,为 0 时表示可回收。优点是实现简单,缺点是无法解决循环引用
- 可达性分析:从一组 GC Roots 出发,向下搜索引用链:
- 能被访问到 → 存活
- 不能被访问到 → 判定为可回收对象
GC Roots 常见有哪些
- 栈中引用(方法局部变量)
- 方法区中的静态变量
- 常量池引用
- JNI(本地方法)引用
GC
GC 是 JVM 自动管理内存的机制,负责回收不再使用的对象,防止内存泄漏。
常见的垃圾回收算法
1)标记-清除(Mark-Sweep)
标记垃圾对象 → 直接清除 缺点:产生内存碎片
2)标记-整理(Mark-Compact)
标记后把存活对象向一端移动
优点:无碎片
缺点:有移动成本
3)复制算法(Copying)
把内存分两块,每次只用一块
存活对象复制到另一块
优点:无碎片、效率高
缺点:空间浪费
分代回收
分代回收的核心思想是:根据对象存活时间不同,采用不同回收策略,提高 GC 效率因为大多数对象具有“朝生夕死”的特点。
不同对象生命周期不同:
- 新创建的对象 → 大概率很快被回收
- 存活较久的对象 → 很少被回收 如果统一用一种算法,效率会很低,新生代使用复制算法,老年代使用标记整理+标记清除
STW在GC中是否必须
STW:在进行某些操作时,暂停所有用户线程(业务线程),只让 GC 线程执行。说是GC停顿更容易理解一点 STW必须存在,但可以尽量缩短时间
为什么必须 STW
1)保证对象引用关系一致
如果 GC 时用户线程还在修改对象引用,会导致:
- 可达性分析结果不准确
- 出现“误回收”或“漏回收”
2)保证内存安全
避免对象在回收过程中被修改,导致数据不一致
能不能完全没有 STW
不能完全消除,但可以减少:
- Serial / Parallel:STW 时间较长
- CMS:并发标记和清除,减少停顿
- G1:通过分区回收,控制停顿时间
常见的GC回收器
1)Serial / Serial Old(串行)
特点:
- 单线程回收
- GC 时完全 STW(Stop-The-World)
- 实现简单 算法:
- 新生代:复制
- 老年代:标记-整理 适用:
- 小堆内存、单核环境
2)ParNew(新生代并行)
特点:
- Serial 的多线程版本
- 仍然 STW,但速度更快 典型搭配:
- 常与 CMS垃圾回收器 一起使用
3)Parallel Scavenge / Parallel Old(吞吐量优先)
特点:
- 多线程并行 GC
- 关注 吞吐量(Throughput)
- 仍然 STW 适用:
- 批处理、计算型任务
4)CMS(Concurrent Mark Sweep)
CMS垃圾回收器 目标:
- 最小化停顿时间 流程:
- 初始标记(STW短暂)
- 并发标记
- 重新标记(STW)
- 并发清除 问题:
- 内存碎片
- 并发失败(退化为 Full GC)
- 已逐步被替代
5)G1(Garbage First,主流)
G1 将堆划分为多个大小相等的 Region,在并发标记阶段统计各 Region 的存活情况,在 Mixed GC 时按照回收收益进行排序,并在用户设定的停顿时间目标内选择一部分 Region 进行回收,从而实现可预测的停顿时间。 核心:
- Region 分区
- 优先回收垃圾最多的区域 特点:
- 可控停顿时间
- 混合回收(新生代 + 老年代)
- 并发 + 局部 STW 适用:
- 大多数服务端应用(默认)
6)ZGC(低延迟,了解)
ZGC垃圾回收器 特点:
- 几乎全并发
- 停顿时间极低(毫秒级)
- 使用着色指针 适用:
- 超大堆、对延迟敏感系统
7)Shenandoah(低延迟,了解)
Shenandoah垃圾回收器 特点:
- 并发标记 + 并发整理
- 停顿时间稳定
面经地址
https://www.nowcoder.com/feed/main/detail/a4238678a5d743fbb8c4fd4eada85ab3?sourceSSR=users
#面经#记录每天Java和Agent面经学习
