<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Java on 安橙的博客</title><link>https://blog.ans20xx.com/tags/java/</link><description>Recent content in Java on 安橙的博客</description><generator>Hugo -- 0.163.3</generator><language>zh</language><lastBuildDate>Sat, 21 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.ans20xx.com/tags/java/index.xml" rel="self" type="application/rss+xml"/><item><title>Java IO</title><link>https://blog.ans20xx.com/posts/backend/java-io/</link><pubDate>Sat, 21 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.ans20xx.com/posts/backend/java-io/</guid><description>&lt;h1 id="io-模型与概念框架"&gt;IO 模型与概念框架&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-78165234"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-78165234" style="display:none;"&gt;
- IO 模型与概念框架
- 四个基本概念
- 阻塞 vs 非阻塞
- 定义：一次调用在数据未就绪时是否立即返回
- 阻塞：调用线程进入等待，直到条件满足才返回，例如：InputStream.read()，数据没到就卡住
- 非阻塞：调用立即返回，不等数据
- 读：没数据时返回 0/-1 或抛特定异常
- 写：写不完就写一部分，返回已写字节数
- JavaNIO
- SocketChannel.configureBlocking(false) 之后，read() 不会阻塞，可能返回 0
- 阻塞非阻塞指的就是线程是否在这次调用上被卡主
- 同步 vs 异步
- 定义：结果是由谁来完成并通知
- 同步：调用负责发起&amp;#43;等待/轮询&amp;#43;拿结果，即使是非阻塞轮询，仍然是同步，因为结果获取的责任方在调用方
- 异步：调用方发起后立即返回，后续由系统/框架在操作完成时回调/事件通知取结果，调用方不需要自己轮询等待结果
- JavaNIO
- AsynchronousSocketChannel &amp;#43; CompletionHandler 是典型的 AIO 异步模型：完成时回调你
- 四象限组合
- 同步阻塞：BIO（经典 read() 卡住直到有数据）
- 同步非阻塞：NIO 非阻塞 read() &amp;#43; 你自己轮询/配合 Selector 处理就绪事件，本质仍然是调用方驱动流程
- 异步非阻塞：AIO（回调通知你“读完了/写完了“）
- 异步阻塞：理论上存在，比如发起异步后又 get() 阻塞等待，但是工程上不这么用
- BIO/NIO/AIO 的准确定义
- BIO(Blocking IO)
- API 代表：ServerSocket/Socket、InputStream/OutputStream
- 特征：accept()/read()/write() 默认阻塞
- 典型服务端模型：1 连接 1 线程（或线程池&amp;#43;阻塞读写）
- 痛点：
- 连接数上来后线程数膨胀：上下文切换、栈内存、调度开销
- 大量线程处于阻塞态，浪费资源
- NIO(Non-blocking I/O &amp;#43; Multiplexing)
- API 代表：Channel/Buffer/Selector
- 关键点：非阻塞 Channel &amp;#43; Selector 多路复用
- 本质：少量线程监听大量连接的就绪事件，再去执行非阻塞读写
- 价值：
- 连接多时不需要等比例增加线程
- 事件驱动，提升并发连接处理能力
- NIO 三件套
- Channel：数据容器，可读可写
- Buffer：数据容器
- Selector：事件分发器
- AIO(Asynchronous I/O)
- API 代表 AsynchronousServerSocketChannel/AsynchronousSocketChannel
- 特征：发起读写后立即返回，完成后通过 CompletionHandler 回调通知结果
- 工程事实：Java AIO 在不同 OS 上底层支持与效果不同；很多高性能网络主流仍然是 Netty（基于 NIO 的 Reactor &amp;#43; 工程化）
- 多路复用（IO Multiplexing）
- 核心：一个线程可以等待多个 fd 的事件
- 没有多路复用：要等待某个 socket 可读，只能在这个 socket 上阻塞 read
- 有多路复用：线程阻塞在 selector/poll/epoll_wait 上，内核告诉你那些 fd 可读/可写，再去处理
- Java NIO 的 Selector.select()=在做等事件，底层映射到 OS 的 select/poll/epoll/kqueue
- Reactor 模式（NIO 常见线程模型）
- Reactor：事件循环(event loop) &amp;#43; 分发(dispatch)
- 流程
- selector.select() 事件
- 拿到 selected keys
- 根据事件类型调用对于 handler
- 单 Reactor 单线程：简单，适合低负载
- 主从 Reactor(boss/worker)：boss 负责 accept，worker 负责读写
&lt;/textarea&gt;
&lt;h1 id="java-bio"&gt;Java BIO&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-78642315"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-78642315" style="display:none;"&gt;
- Java BIO
- IO 流体系
- 两大抽象
- 字节流：InputStream/OutputStream
- 面向原始字节
- 字符流：Reader/Writer
- 面向字符，内部会涉及字符集解码/编码
- 涉及文本语义/字符集，就优先用 Reader/Writer 或者用 InputStream &amp;#43; 明确 Charset 解码；处理二进制就用 InputStream/OutputStream
- 节点流 vs 处理流
- 节点流：直接连接数据源/目的地
- FileInputStream、FileOutputStream、Socket.getInputStream
- 处理流：包装节点流，增强能力（缓冲、转码、数据类型、对象序列化、压缩、校验）
- BufferedInputStream、InputStreamReader、DataInputStream、ObjectInputStream
- 装饰器模式：不改原类，通过包装叠加功能
- Buffered 缓冲为什么快
- 不加缓冲为什么慢
- 许多 read()/write() 最终都会调用 OS 层的 read(2)/write(2) 系统调用，系统开销不小
- 不缓冲：每读 1KB 就要系统调用一次，次数巨大
- 加缓冲：一次系统调用读 8KB/64KB 放到用户态缓冲区，后续小读都在内存中完成，降低系统调用次数
- BufferedInputStream/BufferedOutputStream
- BufferedInputStream：内部有 byte[] buffer（默认 8KB），先批量从底层读，再按需给你
- BufferedOutputStream：先写入 buffer，满了再 flush 到底层，减少了 write 的系统调用次数
- flush 是什么
- 把用户缓冲区里的数据推下去
- flush() != 一定落盘
- 字符集与桥接流
- InputStreamReader/OutputStreamWriter
- InputStreamReader：字节 -&amp;gt; 字符（解码），需要 Charset
- OutputStreamWriter：字符 -&amp;gt; 字节（编码），需要 Charset
- 典型用法
- 读文本
- BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(f), UTF_8))
- 写文本
- BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), UTF_8))
- 为什么不能随便 new String(byte[])
- 不指定 charset，会走平台默认编码，导致乱码
- 常见功能处理流
- DataInputStream/DataOutputStream（理解二进制协议）
- 能按固定类型读写：writeInt/readInt，writeLong/readLong
- 用途：自定义二进制协议、文件格式
- DataInputStream 读写的是 Java 规定格式，跨语言要慎重，更常见的跨语言协议是 protobuf、msgpack 等
- ObjectInputStream/ObjectOutputStream
- Java 原生序列化：实现 Serializable
- 问题：性能一般、版本兼容差、安全风险
- 不建议用于 RPC/外部输入，内部常见简单可用，生产用 protobuf/kryo/json 等
- PrintWriter/PrintStream（输出遍历但注意编码/flush）
- System.out 是 PrintStream
- PrintWriter 可选 autoFlush（遇到 println/printf/format 自动 flush）
- PrintWriter 默认吞异常
- 资源管理与异常
- try-with-resources
- 自动 close，且 close 顺序与声明顺序相反
- close 过程中异常会作为 suppressed 异常附加到主屏幕
- close vs flush
- close vs flush
- close() 会隐式 flush()，不能依赖进程崩溃前的 close
- 网络场景：写完重要数据要主动 flush 或者使用协议层 ack。
- BIO 与网络 IO：Socket 阻塞读写的关键点
- 阻塞读到集中返回情况
- InputStream.read(byte[])
- 返回 &amp;gt;0：读到的字节数
- 返回 -1：对端关闭输出
- 可能一直阻塞：对端不发数据也不关
- 半包/粘包在 BIO 也存在
- BIO 只是阻塞，TCP 仍然是字节流，read() 不保证一条业务消息完整到达
&lt;/textarea&gt;
&lt;h1 id="nio-基础-bufferchannel"&gt;NIO 基础 Buffer/Channel&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-21486573"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-21486573" style="display:none;"&gt;
- NIO 基础
- NIO 的核心心智模型：Buffer&amp;#43;Channel
- 在 NIO 中，数据永远在 Buffer 里转
- 读：Channel.read(ByteBuffer) 把数据写进 buffer
- 写：Channel.write(ByteBuffer) 从 buffer 读出数据写到 channel
- Channel 是管道，Buffer 是货箱，NIO 必须管理 buffer 状态
- ByteBuffer 属性与状态
- 关键属性
- capacity：容量，创建后固定
- position：当前位置（下次读/写从这里开始）
- limit：边界（读/写不能超过它）
- mark：标记位置（可选）
- remaing()：limit - position
- 两种模式
- 写模式：准备往 buffer 里放数据（例如从 channel read 进来）
- position 向后走，limit 通常等于 capacity
- 读模式：准备从 buffer 里取数据（例如写到 channel 或解析协议）
- position 向后走，limit 表示可读数据的末尾
- 三个最重要的方法
- flip：写模式 -&amp;gt; 读模式
- 把 limit 设置为当前 position，再把 position 设置为 0
- 我写完了，现在从头读刚写的数据
- clear：读完后重置为写模式（不清数据，只重置指针）
- position = 0; limit = capacity; mark = -1
- 不关心旧数据了，直接当成空 buffer 继续写
- compact：读了一部分，保留剩余未读数据，并切换为写模式
- 把未读数据 [position,limit) 移动到开头
- position=remaining;limit=capacity
- 我还剩一点没处理，先挪到前面，下次继续在后面写新数据
- HeapBuffer vs DirectBuffer
- Heap ByteBuffer（堆内）
- ByteBuffer.allocate(n)
- 好处：分配快，受 JVM GC 管理，排查简单
- 坏处：做网络 IO 时需要把数据从堆复制到内核缓冲区
- Direct ByteBuffer（堆外）
- ByteBuffer.allocate(n)
- 好处：减少一次拷贝，适合大块网络 IO，频繁 IO。
- 坏处
- 分配/释放更贵
- 受 MaxDirectMemorySize 限制
- 回收依赖 Cleaner/GC 时机，不是=null 就立刻释放
- Channel：FileChannel 与 SocketChannel
- FileChannel
- 来源
- FileInputStream.getChannel()
- FileOutputStream.getChannel()
- 常用能力
- read(ByteBuffer)/write(ByteBuffer)
- position()/position(long)：随机读写位置
- size() 文件大小
- force(boolean metaData)：把修改刷到存储设备
- transferTo/transferFrom：为后续零拷贝铺垫
- SocketChannel
- 支持 configureBlocking(false) NIO 非阻塞基石
- NIO 文件读写的标准流程
- FileChannel 读取
- buffer.clear() 进入写模式
- int n = channel.read(buffer) 把数据写进 buffer
- buffer.flip() 进入读模式
- 从 buffer 读取处理
- 循环直到 n == -1
&lt;/textarea&gt;
&lt;h1 id="nio-进阶-selector多路复用"&gt;NIO 进阶 Selector/多路复用&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-41753268"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-41753268" style="display:none;"&gt;
- NIO 进阶 Selector/多路复用
- Selector/SelectionKey/Channel：三者关系与生命周期
- 三件套职责
- Selector：事件复用器，阻塞在 select() 等待就绪事件，把就绪的 key 放进 selectedKeys 集合
- SelectableChannel：可注册到 Selector 的通道
- SelectionKey：一次注册的凭证/句柄，包含
- channel()：对于 channel
- selector()：归属 selector
- interestOps()：你关心那些事件
- readyOps()：当前就绪了哪些事件
- attachment()：绑定业务状态
- 注册与事件的基本规则
- channel.register(selector, OP_READ, attachment)：把 channel 注册给 selector，并声明兴趣事件
- interestOps 是订阅，可以动态改
- readyOps 是本轮 select 的结果，不能直接改
- key 的取消与资源释放
- key.cancel()：从 selector 的 key 集合中移除
- 移动要同时 close channel：channel.close()，否则 fd 泄露
- 被 cancel 的 key 不会立刻从所有集合消失，通常在下一次 select 时清理
- 多路复用：就绪事件语义
- 就绪不是完成
- OP_READ 就绪：表示现在读不阻塞，不保证读到一条完整的业务消息
- OP_WRITE 就绪：表示现在写更可能成功，不保证一次 write 写完你要写的所有数据
- 各个事件含义
- OP_ACCEPT：ServerSocketChannel 有新连接可以 accept
- OP_CONNECT：客户端连接建立过程完成（非阻塞 connect）
- OP_READ：SocketChannel 可读
- OP_WRITE：SocketChannel 可写（注意：很多时候“总是可写”）
- Reactor 模式：从单线程到主从 Reactor
- 单线程 Reactor
- 一个线程做三件事
- selector.select() 等待事件
- 遍历 selectedKeys
- 按事件类型分发处理：accept/read/write
- 优点：简单
- 缺点：业务处理慢会阻塞整个事件循环
- 主从 Reactor（boss/worker）
- boss：只负责 accept，把新 SocketChannel 分发到 worker 的 selector
- worker：负责读写与协议解析、业务派发
&lt;/textarea&gt;
&lt;h1 id="netty"&gt;Netty&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-16542738"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-16542738" style="display:none;"&gt;
- Netty
- 解决什么问题
- 原生 NIO 的痛点
- 需要自己管理：Selector 轮询、Key 生命周期、半包/粘包、写不完与 OP_WRITE 开关
- Buffer 管理复杂：flip/compact、输入输出缓冲复用、DirectBuffer 回收时机
- 性能与稳定性：Selector 空轮询、内存膨胀、背压、线程隔离
- Netty 提供的工程化能力
- 线程模型（EventLoopGroup &amp;#43; Reactor）成熟
- Pipeline/Handler 体系：解码、业务、编码职责分离
- ByteBuf：读写指针友好、池化、零拷贝视图、引用计数
- 编解码器、背压、写队列、水位线、内存泄露检测等工具链
- 线程模型
- EventLoop
- EventLoop = 一个线程 &amp;#43; 一个 Selector &amp;#43; 一组 Channel
- EventLoop 负责
- 处理 IO 事件
- 执行任务队列
- 同一个 Channel 的所有 Handler 回调默认都在通一个 EventLoop 线程执行，一致性，减少锁
- EventLoopGroup
- bossGroup：负责 accept（ServerSocketChannel）
- workerGroup：负责已建立连接的读写（SocketChannel）
- 为什么少量线程能抗大量连接
- 事件驱动：线程主要阻塞在 epoll/kqueue 上，事件就绪才处理
- 连接多不会导致线程线性增长
- Channel、Future、Promise：异步编程语义
- ChannelFuture
- Netty 大量 API 返回 ChannelFuture，比如 bind、connect、writeAndFlush，表示操作已发起，完成会通知
- future.addListener(...)：完成回调（推荐）
- future.sync()：阻塞等待
- Promise
- 可写的 future，用于自己控制完成/失败
- Pipeline/Handler：职责分离
- Pipeline 是什么
- 每个 Channel 都有一条 ChannelPipeline
- Inbound（入站）：数据从 socket 进来 -&amp;gt; 解码 -&amp;gt; 业务处理
- Outbound（出站）：业务写出 -&amp;gt; 编码 -&amp;gt; flush 到 socket
- Handler 分类
- ChannelInboundHandler：处理入站事件（channelRead、channelActive…）
- ChannelOutboundHandler：处理出站事件（write、flush…）
- ChannelDuplexHandler：双向
- SimpleChannelInboundHandler&amp;lt;T&amp;gt;：自动释放入站消息（配合 ByteBuf 引用计数要理解）
- ByteBuf
- 为什么 ByteBuf 比 ByteBuffer 友好
- 两个指针：readerIndex/writerIndex
- 不需要 flip：写完读直接通过读指针移动
- 支持切片/复制语义清晰
- 三种常见 ByteBuf
- Heap：堆内
- Direct：堆外
- CompositeByteBuf：多个 buffer 组合成一个逻辑 buffer（减少拷贝）
&lt;/textarea&gt;
&lt;h1 id="selectpollepoll"&gt;select/poll/epoll&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-28631457"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-28631457" style="display:none;"&gt;
- select/poll/epoll
- 简介
- 都是操作系统提供的 I/O 多路复用机制
- 让一个线程同时等待很多文件描述符的可读/可写/异常事件，然后再去对就绪的 fd 做 read/write
- 术语
- fd：内核里对打开的文件/套接字等资源的编号。网络 socket 也是 fd。
- 就绪：表示现在读/写大概率不会阻塞，不等于你一次就能读到完整业务包/一次写完所有数据
- select：最老牌的多路复用
- 工作方式：把三组 fd（读/写/异常） 集合传给内核，内核扫描那些就绪，然后返回就绪集合。
- 特点：
- fd 数量上限：select 受到 FD_SETSIZE 限制，常见 1024
- 每次调用要拷贝与扫描：用户态要把位图/集合传入内核，内核要线性扫描
- 集合会被修改：返回时就绪集合是被改过的，下次调用要重新准备集合
- 适用：
- fd 少，跨平台，简单场景。
- poll：对 select 的数组版改良
- 工作方式：把 fd 列表放在一个 pollfd[] 数组传递给内核，内核扫描并在数组里标记那些就绪。
- 相比 select 改进
- 不收 FD_SETSIZE 位图限制
- 表达更灵活
- 缺点
- 仍然是 O(n) 扫描
- 每次调用仍然需要把数组从用户态传给内核
- epoll：为海量连接而生
- epoll 是 Linux 的高性能事件通知机制，核心思想是：把关注列表留在内核里，避免每次把大集合传来传去，返回就绪列表而不是让自己全量扫描
- 关键点
- 关注列表在内核维护：避免每轮 select/poll 大集合拷贝&amp;#43;扫描
- epoll_wait 返回的是就绪事件列表，只处理这些 fd，更接近 O(就绪数)
- LT vs ET 触发模式
- LT (Level-Triggered 水平触发，默认)
- 只要还可读/可写，就会持续通知你
- 编程更简单：读不完下次还会通知你
- ET (Edge-Triggered，边缘触发)
- 只有从不可读 -&amp;gt; 可读的边缘变化才通知一次
- 必须把数据一次性读到不能再读，否则可能丢通知导致卡住
- 性能更高，但对实现要求更严格（非阻塞&amp;#43;循环读/写）
&lt;/textarea&gt;</description></item><item><title>Java 并发复习</title><link>https://blog.ans20xx.com/posts/backend/java-%E5%B9%B6%E5%8F%91/</link><pubDate>Fri, 20 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.ans20xx.com/posts/backend/java-%E5%B9%B6%E5%8F%91/</guid><description>&lt;h1 id="并发基础与-jmm-入门"&gt;并发基础与 JMM 入门&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-14326785"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-14326785" style="display:none;"&gt;
- 并发基础与 JMM 入门
- 并发三大问题
- 原子性
- 定义：一个操作要么全部完成，要么完全不完成，中间过程对其他线程不可见
- 典型反例：i&amp;#43;&amp;#43; 不是原子操作
- 可见性
- 定义：一个线程对共享变量的修改，能否及时被其他线程看到
- 根因
- CPU 缓存(L1/L2/L3) 与编译器/CPU 重排序
- 线程可能一直读到自己缓存中的旧值
- 解决方法
- volatile: 保证写入对其他线程可见
- synchronized/Lock：解锁前的写对之后加锁的读可见
- final：初始化安全
- 有序性
- 定义：代码执行顺序是否一定和你写的一样
- 根因
- 编译器优化：指令重排(JIT)
- CPU 乱序执行
- JMM(Java 内存模型)
- JMM 解决的问题
- JMM 的目标不是描述硬件，而是给 Java 程序提供一个跨平台一致的并发可见性与有序性规范
- 规范：
- 写的代码跑在不同 CPU 上，缓存协议，重排序规则不同
- JMM 通过 happens-before 规定：只要满足 HB，结果必须可见且有序；不满足 HB，结果运行不可预测
- HB = Java 并发正确性的法律条文
- happends-before (HB)
- HB 是可见性 &amp;#43; 有序性的保证关系
- A happens-before B
- A 的结果对 B 可见
- A 的执行顺序对 B 先行约束
- 没有 HB，就不保证可见/有序
- happends-before 规则
- 程序次序规则
- 同一个线程能，按程序顺序，前面的操作 HB 后面的操作
- 监视器锁规则
- 对同一把锁，unlock HB 之后对同一锁的 lock
- volatile 变量规则
- 对同一 volatile 变量，对 volatile 的写 HB 后续对该 volatile 的读
- 线程启动规则
- Thread.start() HB 该线程内的所有操作
- 线程终止规则
- 线程内所有操作 HB 其他线程成功从 Thread.join() 返回
- 中断规则
- 对线程调用 interrupt() HB 被中断线程检测到中断
- 传递性
- 若 A HB B，B HB C，则 A HB C
- volatile
- volatile 保证什么
- 可见性：写入会刷新到主内存，读取会从主内存获取
- 有序性：禁止 volatile 写/读周围的特定重排序（内存屏障）
- 建立 happens-before：写 HB 读
- volatile 不保证
- 不保证复合操作原子性
- 不保证所有代码不重拍：只约束与 volatile 相关的重排边界
- volatile 的经典使用场景
- 状态标志：停止线程 volatile boolean stop
- 单例的安全发布
- 读多写少的配置刷新
- DLC（双重检查锁）为什么必须加 volatile
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/20/20260220165657231.png,292,241)
- new Singleton() 在底层上被视为三步：
- 分配内存
- 调用构造器初始化对象
- 把引用赋给 instance
- 如果发生重排序，可能变为
- 分配内存
- 赋引用给 instance
- 初始化对象
- 另一个线程看到 instance != null 就返回，但是拿到半初始化对象
- volatile 禁止这种关键重排并建立 HB，确保安全发布
- final 的初始化安全
- final 字段在构造完成后有更强的可见性保障
&lt;/textarea&gt;
&lt;h1 id="线程模型与线程安全"&gt;线程模型与线程安全&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-21568437"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-21568437" style="display:none;"&gt;
- 线程模型与线程安全
- Java 线程模型：Thread/Runnable/Callable/Future
- 四者关系与使用场景
- Thread: 线程载体&amp;#43;执行单元
- Runnable：无返回值、不能抛受检异常
- Callable&amp;lt;V&amp;gt;: 有返回值，可抛异常（配合 Future）
- Future&amp;lt;V&amp;gt;：异步结果句柄（拿结果、取消、超时）
- Thread 是执行容器，Runnable/Callable 是任务；Callable 比 Runnable 多返回值与异常；Future 是任务的控制面板
- Future API
- get()：阻塞拿结果
- get(timeout, unit)：超时等待
- cancel(true/false)：尝试取消
- true：会向线程发 interrupt
- false：不 interrupt，只是标记取消
- isDone()/isCancelled()
- 线程生命周期与状态
- Java Thread.State
- NEW：未 start
- RUNNABLE：可运行（包括运行中&amp;#43;就绪）
- BLOCKED：等待获取 monitor 锁（synchronized 竞争）
- WAITING：无限期等待（Object.wait()/Thread.join()/LockSupport.park()）
- TIME_WAITED：限时等待（sleep()/wait(timeout)/join(timeout)/parkNanos）
- TERMINATED：结束
- BLOCKED 和 WAITING 的区别
- BLOCKED：卡在 synchronized，等锁
- WAITING：已经拿到锁，主动等待某个条件
- 线程切换成本
- 上下文切换：保存恢复寄存器、栈、调度
- CPU cache 命中下降
- 线程越多，竞争越激烈，吞吐反而下降
- 中断机制：interrupt 不是杀进程
- 三个 API 的语义
- thread.interrupt()：给线程设置中断标志位
- thread.isInterrupted()：读取中断标志位（不清除）
- Thread.interrupted()：读取当前线程的中断标志位，并且清除
- 那些操作对中断敏感
- 会抛 InterruptedException （清除中断标志）
- Object.wait()
- Thread.sleep()
- Thread.join()
- BlockingQueue.put/take 等可中断阻塞
- Lock 的可中断
- lockInterruptibly()：阻塞等锁时可响应 interrupt
- lock()：等锁期间不相应 interrupt
- 优雅停线程
- 循环里检查中断标志
- 捕获 InterruptedException 后要么退出，要么重新设置中断标志位再退出/上抛
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/20/20260220171246171.png,387,319)
- 线程安全方法论
- 不变性
- 对象状态创建后不再改变
- 示例： String、Interger
- 业务做法：配置对象、DTO 用不可变 &amp;#43; 构造完成后安全发布
- 优点：天然线程安全、无锁高性能
- 缺点：需要复制，内存开销上升
- 线程封闭
- 数据只在一个线程访问
- 典型：方法栈内局部变量、线程私有变量
- 示例：每个线程一个 SimpleDateFormat
- 同步互斥
- 通过临界区序列化访问共享状态
- 无锁/低锁
- 原子类/CAS、分段化
- Synchronized
- Synchronized 锁的是什么
- synchronized(obj)：锁 obj 的 monitor
- synchronized 实例方法：锁 this
- static synchronized: 锁 Class 对象（xxx.class）
- 锁的是对象的 monitor，不是代码块本身
- synchornized 三大语义
- 互斥：同一时刻只有一个线程进入临界区
- 可见性：释放锁前的写，对之后获取同一锁的线程可见
- 可重入：同一线程可重复获得同一把锁
- 锁升级
- 目标：在无竞争/低竞争时，让 synchronzied 尽可能便宜；在竞争激烈时保证正确性与吞吐。
- 策略
- 无竞争：尽量不做原子指令/不进内核
- 少量竞争：用 CAS &amp;#43; 自旋解决（避免线程阻塞/唤醒成本）
- 激烈竞争：进入 monitor，阻塞/唤醒
- 对象头与 Mark Word：锁状态存在哪里
- 每个 Java 对象在 HotSpot 里都有对象头，核心是 Mark Word，用于存：
- 哈希码
- GC 分代/标记信息
- 锁相关信息（锁标志位、线程 ID、指针等）
- synchronized 不单独存一个锁对象结构，优先把锁状态编码在对象头里；必要时才膨胀到 monitor(重量级)。
- 四种主要锁形态
- 无锁
- 对象头处于可锁定但未加锁状态
- 第一次进入 synchronized，会走向偏向/轻量的获取逻辑
- 偏向锁
- 适用场景：几乎总是同一个线程反复进入同一把锁
- 核心思想：预期每次都 CAS/自旋，不如直接偏向某个线程
- MarkWord 记录偏向的线程 id
- 同一线程再次进入，只需要快速检查是不是我，几乎零成本
- 偏向锁的获取：
- 第一次进入：尝试把对象头偏向当前线程（轻量 CAS 一次）
- 再次进入：只做线程 ID 校验
- 偏向锁的撤销
- 另一个线程也来竞争这把锁
- 偏向线程已经结束，且有其他线程来加锁
- JVM 需要批量撤销/重偏向
- 轻量级锁
- 适用场景：有短暂竞争，但竞争不激烈；临界区很短
- 核心机制
- 线程在栈上创建一个 Lock Record（锁记录）
- 把对象头的 Mark Word 复制到 Lock Record（保存旧值）
- 用 CAS 尝试把对象头替换为指向 Lock Record 的指针（同时设置轻量锁标志）
- CAS 成功：拿到轻量锁
- CAS 失败：说明有竞争 -&amp;gt; 可能自旋等待 -&amp;gt; 若仍失败/竞争升高 -&amp;gt; 膨胀为重量级锁
- 重量级锁
- 使用场景：竞争激烈，自旋会浪费 CPU，或者线程阻塞更划算
- 核心机制
- 对象关联一个 ObjectMonitor
- 竞争失败的线程进入 EntryList/WaitSet（不同状态的队列）
- 由 OS 互斥/park-unpark 等机制进行阻塞与唤醒
- 特点
- 成本高：涉及线程挂起/唤醒、上下文切换
- 高竞争下更稳定：避免大量线程空转自旋把 CPU 烧穿
- wait/notify：线程协作的基础
- wait/notify 使用规则
- 必须在 synchronized 内调用，否则抛 IllegalMonitorStateException
- wait() 会
- 释放当前 monitor
- 把线程放入该 monitor 的等待队列
- 被唤醒后需要重新竞争锁
- 永远用 while 检查条件，不用 if
- 防止虚假唤醒
- 防止被唤醒时条件已被其他线程改变
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/20/20260220192817406.png,154,106)
- notify vs notifyAll
- notify(): 随机唤醒一个等待线程
- notifyAll(): 唤醒全部等待线程，让它们竞争并再次检查条件
- 多数条件 prefer notifyAll，除非非常确定只存在一种等待条件且不会误唤醒
- wait/notify vs sleep
- sleep()：不释放锁，属于 Thread 方法，主要用于延时
- wait()：释放锁，属于 Object 方法；用于条件等待
- Condition 与 LockSupport：更现代的协作手段
- Condition 相对 wait/notify 的优势
- 可拥有多个条件队列
- API 更清晰 await/signal/singalAll
- 与 ReentrantLock 组合可实现更复杂的并发控制
- LockSupport
- park() 阻塞当前线程
- unpark(thread)：给某个线程发许可，让它从 park 返回
- unpark() 可以先于 park 发生，比 wait/notify 更灵活
- 可中断的生产者-消费者
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/20/20260220194750850.png,375,460)
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/20/20260220194816875.png,451,697)
- Condition 版本
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/20/20260220195331750.png,365,599)
&lt;/textarea&gt;
&lt;h1 id="juc-核心与-aqs"&gt;JUC 核心与 AQS&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-25478316"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-25478316" style="display:none;"&gt;
- JUC 核心与 AQS
- CAS 与原子类
- CAS 是什么
- CAS 本质是一个 CPU 原子指令
- 比较内存位置的值是否等于 expected
- 是则写入 update
- 返回成功/失败
- CAS 解决了什么
- 解决复合更新在并发下的原子性
- 避免锁的阻塞/唤醒，吞吐通常更高
- CAS 的代价
- 自旋重试：竞争大时空转，CPU 飙高
- 可能导致某些线程长期失败
- AtomicInteger/AtomicLong 的工作方式
- AtomicInteger.incrementAndGet()，本质是
- 循环：读旧值 -&amp;gt; 计算新值 -&amp;gt; CAS 尝试更新 -&amp;gt; 失败重试
- 原子类=CAS 自旋 &amp;#43; volatile 语义，失败重试直到成功
- ABA 问题
- 问题描述
- 线程 T1 看到值为 A，准备 CAS 成 B；期间 T2 把 A 改成 C 又改为 A。
- T1 CAS 会成功，它不知道中间发生过变化
- ABA 什么时候有害
- 关心的是值有没与变过，而不是当前值是不是 A
- 解决方案
- 版本号：AtomicStampedReference（值&amp;#43;stamp）
- 也有 AtomicMarkableReference（值&amp;#43;boolean 标记）
- LongAddr 为什么比 AtomicLong 快
- AtomicLong：所有线程竞争同一个 value，CAS 冲突高
- LongAdder：把热点拆分成多个 Cell，线程尽量更新自己的 Cell，最后 sum 时聚合
- AQS：JUC 的核心骨架
- AQS 最重要的三件东西
- state (volatile int)：同步状态
- CLH 变体 FIFO 队列：等待线程排队
- acquire/release 模板方法：统一的获取/释放流程
- AQS 用 state 表示资源，用队列管理竞争失败的线程，用 acquire/release 模板屏蔽排队、阻塞与唤醒细节，同步器只要实现 tryAquire/tryRelease
- AQS 的两种模式
- 独占：一次只允许一个线程获取
- ReentrantLock、ReentrantReadWriteLock 的写锁
- 共享：运行多个线程同时获取
- Semaphore、CountDownLatch、读锁
- acquire/release
- acquire
- tryAcquire() 尝试直接获取资源
- 失败：把当前线程包装成 Node 入队
- 自旋检查是否轮到自己（前驱是 head 且再 tryAquire）
- 若仍然失败，park 阻塞
- 被唤醒后继续循环
- 要点
- 不是一直自旋：会 park
- 唤醒不是广播，通常唤醒一个合适的后继节点，避免惊群
- release
- tryRelease() 修改 state（释放资源）
- 如果释放后资源可用，唤醒队列中的后继节点
- 为什么 AQS 用队列
- 避免一堆线程同时自旋抢锁造成 CPU 爆炸
- FIFO 队列提供基本公平性基础
- park/unpark 做阻塞唤醒，减少上下文切换次数（相对 monitor 更加可控）
- ReentrantLock
- ReentrantLock 相比 synchronized 的能力点
- 可中断：lockInterruptibly()
- 可超时：tryLock(timeout)
- 可选择公平/非公平
- 多 Condition（多个等待队列）
- 缺点：必须手动释放锁，代码更啰嗦
- 公平 vs 非公平
- 公平锁：大致按照队列先来先服务，吞吐更低
- 非公平锁：允许插队，先 CAS 抢一把，吞吐更高，可能造成某些线程等待更久
- 可中断锁为什么重要
- 线程在等锁时被取消/超时，希望立即退出而不是继续卡死
- lock()：等待锁期间不响应 interrupt
- lockInterruptibly()：能响应 interrupt，抛 interruptedException
- Condition：两个队列（同步队列&amp;#43;条件队列）
- Condition 的语义
- await()：释放锁&amp;#43;当前线程进入条件队列&amp;#43;park
- signal()：把条件队列头部线程转移到同步队列
- 线程真正执行
- 被 signal 转移到同步队列
- 重新竞争锁成功 (acquire)
- await 返回继续跑
- 常见同步工具类
- CountDownLatch（一次性门闩）
- state = count
- await()：共享获取，直到 count == 0 全部放行
- countDown()：释放，count--，到 0 唤醒所有等待者
- 适用场景
- 主线程等待多个子任务完成
- 服务启动前等待依赖就绪
- count 到 0 后不能重置
- Semaphore（信号量：限流/资源池）
- state = permits
- acquire()：permits--，不够则排队阻塞
- release()：permits&amp;#43;&amp;#43;，唤醒等待者
- 适用场景
- 限制并发数
- 连接池/令牌桶
- CyclicBarrier（客服用栅栏，非 AQS 典型）
- 让一组线程在栅栏处互相等待，达到数量后一起继续
- 可复用
- 用于并行计算分段后汇总
&lt;/textarea&gt;
&lt;h1 id="线程池与并发工程实战"&gt;线程池与并发工程实战&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-61523748"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-61523748" style="display:none;"&gt;
- 线程池与并发工程实战
- 不用线程池的问题
- 线程创建/销毁成本高（栈、TLAB、调度）
- 不可控：并发暴涨会把机器压垮
- 缺乏治理能力：无法统一命名，监控、拒绝、限流、隔离
- ThreadPoolExecutor
- 七大参数
- corePoolSize（核心线程数）
- 默认不会受
- 任务来时优先创建核心线程直到达到 core
- maximumPoolSize（最大线程数）
- 队列慢后，才会创建非核心线程，直到 max
- 超过 max 再来任务 -&amp;gt; 触发拒绝策略
- keepAliveTime（空闲存活时间）
- 非核心线程空闲超时会回收
- 若允许核心线程超时，则核心线程也会回收
- workQueue （任务队列）
- 决定任务是排队还是扩线程的关键
- threadFactory（线程工厂）
- 线程命名，daemon、优先级、UncaughtExceptionHandler
- 工程强制：必须设置可读名称
- handler（拒绝策略）
- 线程数到 max 且队列满-&amp;gt;触发拒绝
- unit（时间单位）
- 线程池执行流程
- 若 workerCount &amp;lt; corePoolSize
- 创建核心线程执行 task
- 否则尝试入队 workQueue.offer(task)
- 入队成功：等待线程从队列取任务
- 入队失败，且 workerCount &amp;lt; maximumPoolSize
- 创建非核心线程执行 task
- 否则，拒绝策略 handler.rejectExecution(task, executor)
- workQueue 选型
- LinkedBlockingQueue(无界/大容量队列)
- 特点
- 极容易入队成功，导致线程数通常停在 core，不会扩到 max
- 如果队列无界：任务堆积 -&amp;gt; 内存膨胀/延迟飙升，最终 OOM 或雪崩
- 适用：任务流量可控，任务执行稳定，且能接受排队延迟
- ArrayBlockingQueue(有界队列)
- 特点
- 队列有界，能把系统压力显式化
- 延迟更可控，便于做背压与降级
- 线上服务强烈推荐：有界队列&amp;#43;明确拒绝策略
- SynchronousQueue(不存任务，直接提交)
- 特点
- 没容量，offer 必须被 worker 立即 take 才算成功
- 适合快速扩线程，直到 max，然后拒绝
- newCachedThreadPool 就是它，风险是线程数可能飙升
- 拒绝策略
- 四种内置策略
- AbortPolicy（默认）：抛 RejectedExecutionException
- 适合你希望调用方显式失败，触发降级/重试逻辑
- CallerRunsPolicy：调用方自己执行任务
- 适合给上游施加背压（请求线程被拖慢，QPS 自然下降）
- DiscardPolicy：静默丢弃
- 适合：允许丢数据的场景（采样、非关键日志）
- DiscardOldestPolicy：丢弃队列最老任务，再尝试入队
- 适合：追求新任务更重要的场景
- 工程化拒绝策略（建议自定义 handler）
- 记录指标（reject count、queue size、active count）
- 打点日志
- 触发降级
- 投递到补偿通道
- Executors 工厂的问题
- newFixedThreadPool
- 默认用无界 LinkedBlockingQueue：堆积导致 OOM/延迟失控
- newCachedThreadPool
- 默认 SynchronousQueue &amp;#43; max=IntegerMAX_VALUE：线程数可以无限膨胀
- newSingleThreadExecutor
- 无界队列，同样堆积危险
- 生产环境建议显式 new ThreadPoolExecutor：有界队列、合理 max、明确拒绝策略、线程命名与监控
- 线程数怎么配置
- CPU 密集型
- 线程数 = CPU 核心数
- 目标：减少上下文切换
- IO 密集型
- 线程数 = 核心数 * (1&amp;#43;等待时间/计算时间)
- 工程上推荐压测（不同线程数）-&amp;gt; 看吞吐/延迟/CPU/上下文切换-&amp;gt;找拐点
- CompletableFuture
- 解决的问题
- 传统 Future 的痛点
- 只能 get() 阻塞拿结果，组合多个异步任务很痛苦
- 异常处理分散，超时与取消不优雅
- 任务依赖关系需要手写线程/回调
- CompletableFuture 的定位
- 提供声明式的异步流水线
- 支持任务依赖、并行、聚合、异常处理、完成回调
- 核心心智模型
- Stage（阶段）
- 每个 thenXX 都是在构建一个新阶段
- 上一个阶段完成后触发下一个阶段
- 每个阶段都可能
- 产出一个值
- 抛异常
- 被取消
- Completion（完成态）
- Completable 可能以三种方式结束
- 正常完成（有结果）
- 异常完成（exceptionally completed）
- 被取消（cancelled）
- Executor
- 必须管理在哪个线程执行阶段
- 线程池与执行线程
- 默认线程池
- supplyAsync/runAsync：不传 executor 时，默认走 ForkJoinPool.commonPool
- commonPool 是全局共享池：你的业务、第三方库、框架都可能在用
- 工程风险
- 隔离性差
- 阻塞任务不友好
- 排障难
- thenApply vs thenApplyAsync
- thenApply(fn)：默认在完成上游阶段的线程里执行
- thenApplyAsync(fn)：异步执行
- 不传 executor：用 commonPool
- 传 executor：用指定的线程池
- 常用 API 分组
- 创建与启动
- completableFuture(value)：已完成（同步值包装）
- runAsync(Runnable, executor?)：无返回值任务
- supplyAsync(Supplier&amp;lt;T&amp;gt;, executor?)：有返回值任务
- new CompletableFuture&amp;lt;&amp;gt;() &amp;#43; complete/completeExceptionally：手动控制完成
- 转换 map 与消费 consume
- thenApply(fn): T-&amp;gt;U
- thenAccept(consume): 消费结果（无返回）
- thenRun(runnable)：不关心结果，只做后续动作
- 串联
- thenCompose(fn): T-&amp;gt;CompletableFuture&amp;lt;U&amp;gt;，扁平化
- 用于上一个异步结果决定下一个异步请求
- 并行组合
- thenCombine(other, (a,b)-&amp;gt;c)：两个都完成后合并
- allOf(f1,f2,...)：全部完成
- anyOf(f1,f2,...)：任意一个完成
- applyToEither(other, fn)：谁先完成用谁的结果
- acceptEither：谁先完成就消费
- 异常处理
- exceptionally(ex -&amp;gt; fallback)：异常时给兜底值
- handle((res, ex) -&amp;gt; ...)：无论成功失败都处理
- whenComplete((res, ex) -&amp;gt; ...)：做副作用，不改变结果
- 最佳实践
- 主链用 handler 或 exceptionally 做兜底
- whenComplete 做日志与 metrics
&lt;/textarea&gt;
&lt;h1 id="并发容器阻塞队列并发设计"&gt;并发容器、阻塞队列、并发设计&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-74268153"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-74268153" style="display:none;"&gt;
- 并发容器、阻塞队列、并发设计
- 并发容器解决的问题
- 线程安全访问：多线程读写不会破坏结构、不丢数据
- 性能：比 Collections.synchronizedXxx/Hashtable 更高吞吐
- 语义更强：有些容器提供阻塞、延时、优先级等能力
- 选型
- Map -&amp;gt; ConcurrentHashMap
- List -&amp;gt; 读多血少用 CopyOnWriteArrayList
- Queue -&amp;gt; 无界非阻塞 ConcurrentLinkedQueue，有界阻塞用 ArrayBlockingQueue/LinkedBlockingQueue
- Set -&amp;gt; ConcurrentHashMap.newKeySet()/CopyOnWriteArraySet
- Deque -&amp;gt; ConcurrentLinkedDeque/LinkedBlockingDeque
- ConcurrentHashMap
- CHM 为什么比 Hashtable 快
- Hashtable：方法级 sychornized，所有操作串行
- CHM：更细粒度控制
- 大部分读操作无锁
- 写操作只锁定桶/节点，并结合 CAS
- CHM 的核心结构
- 底层是 Node&amp;lt;K,V&amp;gt;[] table（桶数组）
- 桶内结构：链表(node)或红黑树
- 当链表过长且 table 足够大时，会树化降级查找复杂度
- put 的并发控制逻辑
- table 未初始化：先初始化
- 根据 hash 找桶下标 i
- 如果桶为空：尝试 CAS 放入新 Node
- 若桶非空
- 若桶是迁移中：帮助扩容
- 否则对桶头做 synchronzied 局部锁，在桶内插入/更新
- 插入后可能触发 size 技术更新与扩容检查
- CopyOnWriteArrayList(COW)：读多写少
- 核心机制
- 写操作会
- 加锁
- 复制底层数组
- 在新数组上修改
- 用引用替换发布新数组
- 读操作直接读数组快照，无损
- 适用与不适用
- 适用
- 读远多于写
- 不适用
- 写频繁
- 数组很大
- 并发队列：非阻塞 vs 阻塞
- ConcurrentLinkedQueue(CLQ)：非阻塞队列
- 基于 CAS 的链表队列
- 适合：高并发无界、低延迟、允许忙等/自旋语义的场景
- 特点
- offer/poll 非阻塞
- size() 可能是线性复杂度
- BlockingQueue：核心语义
- BlockingQueue 的关键是两种阻塞语义
- 当队列为空：take() 阻塞直到有元素
- 当队列满：put() 阻塞直到有空间
- offer/poll 返回特殊值
- 三大常用阻塞队列
- ArrayBlockingQueue(ABQ)-有界数组队列
- 底层数组环形缓冲区
- 一般用一把锁 &amp;#43; 两个 condition
- 特点
- 有界：最重要的工程优势
- 内存局部性好
- 吞吐稳定，适合线程池工作队列
- 生成服务最常用的稳健默认
- LinkedBlockingQueue(LBQ)-链表队列(可无界/大容量)
- 链表节点存储，内存分配频繁（节点对象）
- 常见实现会用两把锁来提高并行度
- 风险：堆积导致 OOM 与延迟失控
- SynchronousQueue(SQ)-不存储元素的移交队列
- 容量为 0，put 必须等待 take 直接接手
- 特点：几乎不用排队，延迟低
- DelayQueue/PriorityBlockingQueue：定时与优先级
- DelayQueue 延时队列
- 元素实现 Delayed，按到期时间排序
- take() 会阻塞直到最近到期元素到期
- 适用：超时重试、订单超时关闭、缓存过期处理（小规模）
- 工程上：大量定时任务用时间论/调度框架
- PriorityBlockingQueue(优先级队列)
- 无界优先级堆
- 高优先级先出队
&lt;/textarea&gt;</description></item><item><title>Sping 复习</title><link>https://blog.ans20xx.com/posts/backend/spring-%E5%A4%8D%E4%B9%A0/</link><pubDate>Fri, 20 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.ans20xx.com/posts/backend/spring-%E5%A4%8D%E4%B9%A0/</guid><description>&lt;h1 id="spring-core"&gt;Spring Core&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-34562871"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-34562871" style="display:none;"&gt;
- Spring Core（IoC/DI/Bean 生命周期）
- 概述
- Spring 的核心是 IoC 容器，它负责把对象 Bean 的创建、依赖装配、生命周期管理、扩展点回调统一托管
- 写的业务对象只关心需要什么，不关心怎么 new、怎么组装、什么时候初始化/销毁
- SpringIOC = 依赖反转&amp;#43;容器管理对象生命周期
- DI = 容器把依赖注入进来（构造器/Setter/字段）
- Spring 强大来自于：大量可插拔的扩展点（PostProcessor、Aware、Event...）
- 核心概念与组件
- Bean、BeanDefinition、Container
- Bean：被 Spring 容器管理的对象实例
- BeanDefinition：Bean 的配方/说明书，包括
- beanClass 类
- scope (singleton/prototype/...)
- dependsOn、lazyInit、autowireCandidate...
- initMethods/destroyMethod
- 容器(Container)=读取 BeanDefinition-&amp;gt;创建 Bean-&amp;gt;注入依赖-&amp;gt;生命周期回调
- BeanFactory vs ApplicationContext
- BeanFactory: 最底层的 IoC 容器接口，延迟实例化为主（按需 getBean 才撞见）
- ApplicationContext：更企业级，在 BeanFactory 上提供
- 资源加载
- 国际化
- 事件发布(ApplicationEventPublisher)
- AOP/事务等更完整的生态整合
- 通常启动时预实例化单例（非 lazy 的 singletone）
- 装配方式与注入策略
- 常见装配来源
- @Component &amp;#43; @ComponentScan：扫描注册
- @Configuration &amp;#43; @Bean：显式配置注册（配置复杂时使用）
- @Import：导入配置/Selector/Registrar（Boot 自动配置常用）
- XML（旧系统常用）
- 注入方式对比
- 构造器注入（推荐）
- 依赖不可变，对象更健壮
- 便于测试
- 早失败：缺依赖无法启动
- 循环依赖无法解决，早暴露设计问题
- Setter 注入
- 优点：可选依赖，可后置设置
- 缺点：对象可能处于半初始化状态
- 字段注入
- 难测试
- 破坏封装
- 易隐藏循环依赖与设计问题
- Bean 生命周期全链路
- 生命周期流程
- 解析配置 -&amp;gt; 注册 BeanDefinition
- 实例化：反射/构造器创建对象
- 属性填充：进行依赖注入
- Aware 回调
- BeanNameAware/BeanFactoryAware/ApplicationContextAware 等
- BeanPostProcessor#postProcessBeforeInitialization
- 初始化
- @PostConstructor
- InitializingBean#afterPropertiesSet
- 自定义 initMethod
- BeanPostProcessor#postProcessAfterInitialization
- AOP 代理通常在这生成（返回代理对象替代原对象）
- 容器运行期使用
- 容器关闭，销毁回调
- @PreDestroy
- DisposableBean#destroy
- 自定义 destroyMethod
- 超级扩展点
- BeanFactoryPostProcessor(更早、更底层)
- 作用对象：BeanDefinition
- 执行时机：Bean 实例化之前
- 典型用途：
- 修改 BeanDefinition
- 解析配置占位符
- BeanPostProcessor（最常用，最关键）
- 作用对象：Bean 实例
- 执行时机：初始化前后（before/after init）
- 典型用途
- AOP 代理创建
- 注解处理，例如 @Autowired 的处理
- 作用域与懒加载
- singleton vs prototype
- singleton：容器内单例，默认
- prototype：每次 getBean 创建一个新对象，容器只负责创建与注入，不负责销毁
- @Lazy
- 对 singleton：延迟到第一次使用才创建
- 大量 Bean 时可以缩短启动，但是首次请求会变慢
- 依赖解析与注入规则
- @Autowired 的匹配策略
- 先按类型找候选 Bean
- 如果有多个
- @Primary 优先
- @Qualifier(&amp;#34;name&amp;#34;) 指定
- 再不行：按照字段名/参数名尝试匹配
- 找不到：默认报错，可以用 required=false 或 Optional&amp;lt;T&amp;gt; 表示可选
- @Resource vs @Autowired
- @Resource：默认按 name，再按照 type
- @Autowired(spring)：默认按 type，可以配合 @Qualifier
- 循环依赖
- 什么是循环依赖
- A 依赖 B，B 依赖 A，常在字段/Setter 注入中
- Spring 为什么能解决部分循环依赖
- 依赖注入发生在实例化之后
- 容器允许暴露一个早期引用给对方注入，从而打破死循环
- 三级缓存
- 一级缓存：完整初始化好的单例 Bean
- 二级缓存：早期单例 Bean（半成本引用，可能是原对象，也可能是代理的早期引用）
- 三级缓存：ObjectFactory（工厂），用于需要时才创建早期引用，并且能让 AOP 有机会在早期阶段接入
- 典型场景
- A 依赖 B，B 依赖 A，流程用容器创建 A 为起点
- 触发创建 A
- 调用 getBean(&amp;#34;A&amp;#34;)
- 以及缓存没有 A，进入 createBean(&amp;#34;A&amp;#34;)
- 实例化 A（还没有注入依赖）
- Spring 先 instantiate （反射/构造器）得到 A 的空壳对象 A_raw
- 提前暴露
- 在进入属性注入之前，Spring 会做循环依赖防护的准备
- 向三级缓存放入 singletonFactories[&amp;#34;A&amp;#34;] = ObjectFactory
- 这个 ObjectFactory 的职责是：别人需要 A 的早期引用时，能返回 A_raw 或 A_early_Proxy
- 此时，A 还没被放入二级缓存，只是放了一个可生产早期引用的工厂到三级缓存
- 给 A 做属性填充
- A 需要注入 B -&amp;gt; 容器去 getBean(&amp;#34;B&amp;#34;)
- 创建 B
- getBean(&amp;#34;B&amp;#34;) 缓存都没有 -&amp;gt; createBean(&amp;#34;B&amp;#34;)
- 实例化得到 B_raw
- 为 B 做提前暴露准备
- 开始 B 注入依赖
- B 注入 A（触发早期引用获取）
- B 需要 A -&amp;gt; 容器调用 getBean(&amp;#34;A&amp;#34;)
- A 仍然在创建中
- 一级缓存 singletonObjects：没有
- 但容器发现 A &amp;#34;currently in creation&amp;#34; 会拿走 getSingleton(&amp;#34;A&amp;#34;, allowEarlyReference=true) 的分支，尝试拿 earlyreference（给 AOP 留入口）
- 拿到 earlyRef 后，放入二级缓存，移除三级缓存，返回 earlyRef 给 B 注入
- A 完成初始化，进入一级缓存
- A 继续完成初始化回调链/BPP afterInitialization
- 为什么一定要三级
- 如果只有二级，无法优雅处理什么时候生成代理/早期暴露代理问题
- 三级缓存工厂，让 Spring 能够在真正需要注入时再决定暴露原对象还是代理对象
- 哪些循环依赖 Spring 解决不了
- 构造器注入的循环依赖：实例化阶段就互相需要对方，无法先构造空壳
- prototype 的循环依赖：每次创建都新对象，不走单例缓存那套
- @Configuration 为什么特殊（CGLIB 增强）
- 同一个 @Bean 方法调用两次
- 普通类写 @Bean 方法，直接调用方法会 new 两次
- 在 @Configuration 类中，Spring 会用 CGLIB 增强它
- @Bean 方法被拦截
- 如果容器里已有该 Bean，就返回容器里的单例，而不是再次执行方法创建
&lt;/textarea&gt;
&lt;h1 id="spring-aop"&gt;Spring AOP&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-58316427"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-58316427" style="display:none;"&gt;
- Spring AOP
- AOP 在 Spring 里解决什么问题
- 目标：把横切关注点从业务逻辑中剥离出来，日志，鉴权，事务，监控，限流，缓存，重试等
- Spring AOP 的定位：基于运行时代理，拦截方法调用，织入增强逻辑
- Spring AOP 只能对方法执行做增强
- 核心术语与对象模型
- Join Point/Pointcut/Advice/Aspect
- JoinPoint：可被拦截的位置。Spring AOP 里基本就是方法执行点。
- Pointcut：切点表达式，决定哪些 JoinPoint 被拦截。常见：execution/@annotation/within/this/target 等
- Advice：增强逻辑，要做什么
- Aspect：切面=Pointcut&amp;#43;Advice 的集合
- Advisor
- Advisor：把 Pointcut 和 Advice 打包成一个可应用单元
- Spring 内部处理的是 Advisor 列表，而不是直接处理注解切面
- 事务就是一个 Advisor
- @Aspect 最终会被解析成多个 Advisor，由容器在创建 Bean 时用这些 Advisor 决定是否生成代理以及代理链顺序
- 代理机制：JDK vs CGLIB
- JDK 动态代理
- 只能代理接口
- 通过 InvocationHandler 拦截接口方法调用
- 优点：标准 JDK 能力、生成快、类结构简单
- 缺点：无接口时不可用，对直接调用实现类方法这类场景不覆盖
- CGLIB 代理
- 通过生成目标类的子类并覆写方法拦截
- 适用于无接口的类
- final 类无法被继承 -&amp;gt; 不能代理
- final 方法、构造器逻辑、私有方法不能被增强
- Spring 如何选择代理方式
- 有接口优先 JDK，没接口用 CGLIB
- 强制开启 CGLIB @EnableAspectJAutoProxy(proxyTargetClass= true)/spring.aop.proxy-target-class=true
- Spring AOP 的工作流程
- Auto Proxy Creator
- AnnotationAwareAspectJAutoProxyCreator
- 本质是一个 BeanPostProcessor
- 介入时机：在 PostProcessAfterInitialization 决定是否为 Bean 创建代理
- 简化流程
- 容器启动：扫描到 @Aspect，解析成多个 Advisor
- 创建普通 BeanX 时
- AutoProxyCreator 收集可应用到 X 的 Advisor
- 如果存在匹配
- 创建代理对象 Proxy(X)
- 将 Advisor 转成 MethodInterceptor 链
- 返回 Proxy 替换原对象放入单例池
- 调用 X.method() 时
- 进入代理 -&amp;gt; 执行 interceptor chain -&amp;gt; 最后调用目标方法
- 切点表达式
- execution
- execution(访问修饰符 返回类型 包名.类名.方法名(参数))
- execution(* com.demo.service..*(..))：service 包以及子包所有方法
- execution(* *..*Service.*(..))：所有以 Service 结尾类的方法
- execution(public * *(..))：所有 public 方法
- 注解切点（工程最常见）
- @annotation(com.demo.Loggable)：方法上标注注解才拦截
- @within(...)：类上注解
- @target(...)：目标对象类型带注解（与代理类型可能有差异
- within/this/target
- within(TypePattern)：按声明类型匹配
- this(Type)：按代理对象类型匹配
- target(Type)：按目标对象类型匹配
- Advice 类型与执行顺序
- 五种 Advice
- @Before：目标方法前
- @After：finally（无论是否异常）
- @AfterReturning：正常返回后
- @AfterThrowing：抛异常后
- @Around：最强，包裹整个调用
- 一个调用的典型顺序
- Around（进入）
- Before
- method 执行
- AfterReturning/AfterThrowing (二选一)
- After
- Around 退出
- 多个切面怎么排序
- @Order 数字越小优先级越高（越外层，越先进入 Around）
- Ordered 接口同理
- 没有 order：可能按注册顺序
- 多个 Around 像洋葱圈，order 小的在最外层
- 代理链/拦截器链
- Spring 会把 Advisor 适配成一组 MethodInterceptor，形成链
- MethodInvocation.proceed() 是关键递归点
- 每个 interceptor 做一段逻辑，然后调用 process() 交给下一个
- 最后一个调用 invokeJoinPoint() 执行目标方法
- AOP 经典失效点
- 自调用失效（No proxy，no AOP）
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/21/20260221112533671.png,183,88)
- 调用 m1() 内部 m2() 是 this.m2()，不会经过代理 -&amp;gt; @Transactional/@Around 不生效
- 解决策略
- 拆分 Bean：把 m2 放到另一个 Spring Bean，通过注入调用
- 通过 AopContext.currentProxy()：获取当前代理再调用，需要 exposeProxy = true，侵入性强
- 注入自身代理
- 非 public 方法
- 直接 new 出来的对象没有 AOP
&lt;/textarea&gt;
&lt;h1 id="spring-transaction"&gt;Spring Transaction&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-68342175"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-68342175" style="display:none;"&gt;
- Spring Transaction
- 定义：Spring 事务本质是 AOP 拦截方法调用，在方法执行前后通过 TransactionManager 控制开启/加入事务、提交/回滚、资源绑定/解绑
- 事务体系的关键对象
- PlatformTransactionManager：事务管理器抽象
- DataSourceTransactionManager
- JpaTransactionManager
- TransactionDefinition：事务定义（传播、隔离、超时、只读）
- TransactionStatus：事务状态（是否新建、是否完成、是否回滚标记等）
- TransactionSynchronzationManager：线程级资源管理
- Spring 事务是线程绑定资源，所以跨线程（异步）默认不会继承事务上下文
- @Transactional 做了什么
- 发生在 AOP 的哪个位置
- @Transactional 会被解析为一个 Advisor
- 方法调用进入代理后由 TransactionInterceptor 拦截
- 会读取事务属性，决定
- 加入已有事务还是新开事务
- 方法结束时 commit 还是 rollback
- 时序
- 进入代理方法
- TransactionInterceptor 读取事务属性
- getTransaction()：根据传播行为决定新开/挂起/加入
- 执行目标方法
- 正常返回 -&amp;gt; commit()
- 抛异常 -&amp;gt; 根据回滚规则 rollback() 或者 commit()
- 传播行为
- 方法 A 调用方法 B 时，事务边界怎么处理
- REQUIRED 默认
- 规则：有事务就加入，没有就新建
- 典型：大多数业务方法
- REQUIRES_NEW
- 规则：总是新建事务，若外部已有事务，则挂起外部事务
- 典型：记录操作日志/发送消息/保存审计记录
- NESTED
- 规则：在外层事务存在时，使用 Savepoint 实现部分回滚
- 外层回滚：内层也回滚
- 内层回滚：回到保存点，外层可继续
- SUPPORTS：有事务就加入，没有就非事务执行
- NOT_SUPPORTED：总是非事务执行；若有事务则挂起
- NEVER：如果存在事务则抛异常
- MANDATORY：必须在事务中运行，否则抛异常
- 隔离级别
- Read Uncommitted：可能脏读
- Read Committed：防脏读
- Repeatable Read：防不可重复读
- Serializable：最强，吞吐最差
- 回滚规则
- 默认规则
- 默认只对 RuntimeException 和 Error 回滚
- 受检异常默认不回滚
- 配置回滚规则
- @Transactional(rollbackFor=Exception.class)：受检异常也回滚
- noRollbackFor = XxxException.class：指定异常不回滚
- 事务提交/回滚的关键机制：线程绑定资源
- TransactionSynchronizedManager (TSM)
- 开启事务时
- 从 DataSource 取 Connection
- Connection.setAutoCommit(false)
- 把 Connection 绑定到当前线程(TSM)
- 同一线程后续 DAO 操作会复用这个 Connection
- 提交/回滚后
- commit/rollback
- 解绑资源，释放连接，恢复 autoCommit 等
&lt;/textarea&gt;
&lt;h1 id="spring-mvc"&gt;Spring MVC&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-83126457"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-83126457" style="display:none;"&gt;
- SpringMVC(请求链路、参数绑定、异常处理)
- 总控思维模型
- Spring MVC 是一个基于 DispatcherServlet 的前端控制器框架，它把一次 HTTP 请求拆分
- 映射（找谁处理）-&amp;gt; 适配（怎么调用）-&amp;gt; 参数解析（怎么组装入参）-&amp;gt; 执行（调用 controller）-&amp;gt; 返回值处理（怎么写响应）-&amp;gt; 异常处理（失败怎么兜底）
- 核心入口是 DispatcherServlet#doDispatch：通过 HandlerMapping 找到 handler，再通过 HandlerAdapter 执行；执行前后分别由 ArgumentResolver 和 ReturnValueHandler 处理出参入参，异常交给 HandlerExceptionResolver 链
- 核心组件和职责
- DispatcherServlet（总入口）
- 接受请求
- 找 Handler（Controller 方法）
- 调 HandlerAdapter 执行
- 走视图解析/写回响应
- 捕获异常并交给异常解析器链
- HandlerMapping（找谁处理）
- RequestMappingHandlerMapping：处理 @RequestMapping/@GetMapping，它会维护映射表：URL&amp;#43;HTTP Method&amp;#43;consumers/produces&amp;#43;headers&amp;#43;params-&amp;gt;HandlerMethod
- HandlerAdapter（怎么调用 Handler）
- RequestMappingHandlerAdapter：负责调用 @RequestMapping 方法
- 使用 HandlerMethodArgumentResolver 解析方法入参
- 使用 HandlerMethodReturnValueHandler 处理返回值
- ViewResolver（视图解析，前后端分离可选）
- 返回 String 视图名才需要（JSP/Thymeleaf）
- REST 场景通常直接 @ResponseBody/@RestController，通过 HttpMessageConverter 写响应
- HttpMessageConverter（对象&amp;lt;-&amp;gt;报文）
- @RequestBody：把 JSON/XML/表单正文 -&amp;gt; Java 对象
- @ResponseBody：把 Java 对象 -&amp;gt; JSON/XML
- 常见 Jackson JSON converter
- 一次请求到完整链路
- 按 DispatcherServlet#doDispatch 的逻辑
- 接受请求（Servlet 容器调用 DiapatcherServlet#service）
- 拿 Handler：遍历 HandlerMapping，找到匹配的 Handler，通常是 HandlerMethod
- 拿 HandlerAdapter：选择能执行该 Handler 的适配器，通常是 RequestMappingHandlerAdapter
- 执行前置
- 绑定/转换：WebDataBinder（类型转换、校验）
- 参数解析：ArgumentResolver
- 拦截器 preHandler（HandlerInterceptor）
- 调用 Controller 方法
- 处理返回值
- REST：ReturnValueHandler -&amp;gt; HttpMessageConverter 写回 JSON
- 视图：返回 ModelAndView -&amp;gt; ViewResolver 渲染
- 执行后置：拦截器 postHandle/afterCompletion
- 异常处理：catch 异常 -&amp;gt; HandlerExceptionResolver 链处理-&amp;gt;统一响应
- 请求映射匹配细节
- @RequestMapping 能参与匹配的额求
- path(含 Ant 风格、PathVariable)
- HTTP method (GET/POST/PUT/DELETE)
- params（必须带参数/参数值）
- headers （必须带 header）
- consumes（请求 content-type）
- produces（响应 Accept/Content-Type）
- 参数绑定
- 请求行/URL/Query/Form 类
- @PathVariable：从 URL 路径中取值
- @RequestParam：从 queryString 或表单中取值
- @RequestHeader：从 header 取值
- @CookieValue：从 cookie 取值
- HttpServletRequest/Response、Principal 等：直接注入
- 默认绑定
- 对简单类型：往往当做 @RequestParam
- 对复杂类型：走 @ModelAttribute 逻辑，把 query/form 参数按字段名绑定进去
- @RequestBody （正文序列化）
- 请求体 (JSON) 通过 HttpMessageConverter 反序列化成对象
- 常见错误
- Content-Type 不是 application/json
- 字段类型不匹配导致 HtppMessageNotReadableException
- 缺少无餐构造器/反序列化失败
- 类型转换与校验
- 类型转换链路
- ConversionService：负责类型转换
- Converter/Formatter：自定义转换规则
- 校验 JSR-303
- @Valid/@Validated
- 常见异常
- MethodArgumentNotValidException(@RequestBody &amp;#43; @Valid)
- BindException(@ModelAttribute 绑定错误)
- 配合 @ControllerAdvice 做统一错误响应
- 返回值处理
- 常见返回类型
- String：视图名
- ModelAndView：显式指定视图和模型
- ResponseEntity&amp;lt;T&amp;gt;：最推荐的 REST 返回方式之一，可控状态码/headers/body
- T &amp;#43; @ResponseBody：直接序列化成 JSON
- 返回值处理关键点
- @RestController = @Controller &amp;#43; @ResponseBody
- 选择哪个 converter 取决于
- 返回对象类型
- Accept 头
- produces 配置
- 异常处理体系
- HandlerExceptionResolver 链
- 按顺序尝试解析异常
- ExecptionHandlerExceptionResolver：处理 @ExceptionHandler/@ControllerAdvice
- ResponseStatusExceptionResolver：处理 @ResponseStatus
- DefaultHandlerExceptionResolver：处理框架内置异常
- @ControllerAdvice 正确用法
- 全局异常 -&amp;gt; 统一 error code &amp;#43; message &amp;#43; traceId
- 参数校验异常 -&amp;gt; 返回字段级错误列表
- 业务异常 -&amp;gt; 返回业务错误码
- 兜底异常 -&amp;gt; 返回 500 &amp;#43; 记录日志
- Filter vs Interceptor
- Filter（Servlet 规范）
- 发生在 Servlet 容器层面
- 能包裹整个请求响应链
- 用于：编码/CORS/鉴权/请求体包装/日志 traceId 注入
- Interceptor（Spring MVC）
- 发生在 Handler 执行前后，更靠近 Controller
- 包含三段内容
- preHandler（调用前）
- postHandler（方法返回后，视图渲染前）
- afterCompletion（整个请求完成后，用于清理资源/记录异常）
- Filter 是容器级，Interceptor 是框架级，能拿到 HandlerMethod，适合做与业务 Handler 相关的逻辑
&lt;/textarea&gt;
&lt;h1 id="spring-boot-自动配置启动过程外部化配置"&gt;Spring boot （自动配置、启动过程、外部化配置）&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-65382741"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-65382741" style="display:none;"&gt;
- Spring boot
- 解决了什么问题
- Boot 的核心价值是：约定优于配置&amp;#43;自动配置&amp;#43;Starter依赖聚合
- Starter：把一组常用的依赖按照场景打包
- Auto-Configuration：根据 classpath、bean、配置项等条件自动创建 Bean
- Externalized Configuration：同一的多来源配置体系
- Production-ready：Actuator、metrics、health、logging 等
- @SpringBootApplication 注解做了什么
- @SpringBootConfiguration 本质就是 @Configuration
- @ComponentScan 默认扫描启动类所在包及其子包
- @EnableAutoConfiguration：开启自动配置导入
- 自动配置原理
- 概述
- Boot 会在启动阶段把候选自动配置类加载进容器，但是不是无脑全开，而是通过大量的 @Conditional 做条件装配
- 类路径上有没有哪个类
- 容器里有没有某个 Bean
- 配置属性是否存在且满足
- 是否 Web 环境、是否 Servlet/Reactive
- 自动配置类从哪里来
- 自动配置类的来源本质是声明式列表，Boot 在启动时读取他们并导入
- Boot 在启动阶段通过自动配置导入机制读取 classpath 上各个 starter 提供的自动配置声明，然后把这些自动配置类作为配置类导入容器；最终每个自动配置类再通过 @Conditional 决定是否生效
- 条件注解
- @ConditionalOnClass(A.class)：classpath 有 A 才启动
- @ConditionalOnMissingBean(X.class)：容器里没有 X 才创建默认实现
- @ConditionalOnBean(Y.class)：必须已存在 Y 才启用
- @ConditionalOnProperty(prefix=&amp;#34;a&amp;#34;, name=&amp;#34;b)
- @ConditionalOnWebApplication
- 自动配置覆盖/接管机制
- Boot 默认提供一个 Bean，例如某个 DataSource
- 只要自己定义同类型 Bean，通常由于自动配置里用了 @ConditionalOnMissingBean，默认不会创建
- 启动过程
- 启动过程的分段
- 构建 SpringApplication
- 推断应用类型：Servlet/Reactive/None
- 加载初始化器/监听器（Initializers/Listeners）
- 准备 Environment
- 读取配置源：application.yml、环境变量、命令行参数、profile 等
- 绑定 spring.* 相关关键属性（例如 banner、log、profiles）
- 创建 ApplicationContext
- Servlet Web：AnnotationConfigStyleWebServletApplicationContext
- Reactive Web：对于 reactive context
- 准备 Context
- 注册启动类
- 执行 ApplicationContextInitializer
- 刷新 Context
- 进入 Spring Core 经典流程
- 自动配置也在这一阶段被导入并参与 Bean 创建
- 启动 WebServer
- 发布 ApplicationReadyEvent
- 应用可以对外提供服务
- 外部化配置
- 配置来源
- application.properties/application.yml
- profile 文件：application-dev.yml
- 环境变量 ENV
- JVM 系统属性（-Dkey=value）
- 命令行参数(--key=value)
- 优先级
- 越靠近运行时/越具体的优先级越高
- 命令行/系统属性/环境变量高于配置文件
- profile 配置文件会在激活 profile 后叠加
- Boot 会把多来源配置汇总成 Environment 的 PropertySources，按优先级从高到低解析；同名 key 取最高优先级来源的值
- Profile
- 激活方式
- spring.profiles.active=dev
- --spring.profiles.active=dev
- 环境变量
- 生效规则
- application.yml 的默认块&amp;#43;application-dev.yml 覆盖叠加
- 配置绑定 @ConfigurationProperties
- 对比 @Value
- @Value：零散，难管理，不支持复杂层级/校验/IDE提示弱
- @ConfigurationProperties：可以把一个前缀下的配置绑定到一个对象，支持嵌套、集合、校验
&lt;/textarea&gt;
&lt;h1 id="数据访问整合"&gt;数据访问整合&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-26731548"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-26731548" style="display:none;"&gt;
- 数据访问整合
- 通用底座
- DataSource：连接的来源
- DataSource 是连接池的抽象
- Spring 从 DataSource 拿 Connection，但事务期间会把 Connection 绑定到线程，避免每次 DAO 都新拿连接
- 事务贯穿的关键：TransactionSynchronizationManager(TSM)
- 开启事务时：取连接-&amp;gt;setAutoCommit(false)-&amp;gt;bind 到线程
- DAO 执行时：通过 Spring 提供的工具从线程拿到同一连接
- 提交/回滚后：解绑并释放连接
- 同一异常体系：DataAccessException
- Spring 会把各类底层异常统一成 DataAccessException 体系
- 屏蔽不同驱动/框架的异常差异
- 让事务默认回滚更符合直觉
- JDBC: JdbcTemplate
- JdbcTemplate 的价值
- 封装样板代码：拿连接、创建 statement、执行、关闭资源、异常翻译
- 与 Spring 事务自然融合：事务内复用同一连接
- MyBatis 与 Spring 集成
- MyBatis 的核心对象
- SqlSessionFactory：会话工厂
- SqlSession：一次会话（执行 SQL 的入口）
- Mapper：接口代理，最终通过 SqlSession 执行
- Executor：执行器（缓存、批处理等）
- Spring-MyBatis 如何整合
- Spring 提供 SqlSessionTemplate
- 在事务中，通过 Spring 的事务同步机制把 SqlSession 与当前事务绑定
- 事务提交/回滚时，SqlSession 会随着事务一起 commit/rollback
- Mapper 扫描与处理
- @MapperScan 会注册 Mapper 接口代理 Bean
- 调用 Mapper 方法 -&amp;gt; 代理 -&amp;gt; SqlSessionTemplate -&amp;gt; 执行 SQL
&lt;/textarea&gt;
&lt;h1 id="spring-security"&gt;Spring Security&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-72368145"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-72368145" style="display:none;"&gt;
- Spring Security
- 核心概念
- SecurityContext：安全上下文，存当前请求/线程的认证信息
- Authentication：一次认证的结果对象
- principle：用户主体（UserDetail 或 userId）
- credentials：凭证（密码/token）
- authorities：权限集合（角色/权限点）
- authenticated：是否已认证
- SecurityContextHolder：存取 SecurityContext，默认 ThreadLocal
- 认证成功后，SecurityContextHolder 里面会有 Authentication，后续授权判断都基于它
- UserDetails 体系
- UserDetails：用户信息抽象（用户名、密码、权限、是否过期）
- UserDetailsService：按用户名加载用户
- PasswordEncoder：密码编码/校验
- 过滤器链：SecurityFilterChain
- 请求进入一堆 SecurityFilters 才会进入到 SpringMVC
- 认证机制
- AuthenticationManager/ProviderManager
- AuthenticationManager：认证入口
- 常用实现：ProviderManager 内部持有多个 AuthenticationProvider
- AuthenticationProvider：一种认证方式的具体实现
- 认证流程
- 过滤器拿到凭证，构造一个未认证的 Authentication
- 交给 AuthenticationManager
- ProviderManager 遍历 Providers，找到 supports 的 provider
- provider 校验成功 -&amp;gt; 返回已认证的 Authentication
- 写入 SecurityContexHolder
- 后续授权阶段使用
- 授权机制
- authorities/roles 的关系
- Spring Security 里面权限统一叫做 GrantedAuthority
- 角色以 ROLE_ 前缀表示
- hasRole(&amp;#34;ADMIN&amp;#34;) 等价于检查 authority ROLE_ADMIN
- hasAuthority(&amp;#34;xxx:read&amp;#34;) 检查精确权限点
- URL 级授权
&lt;/textarea&gt;</description></item><item><title>JVM 学习</title><link>https://blog.ans20xx.com/posts/backend/jvm/</link><pubDate>Thu, 19 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.ans20xx.com/posts/backend/jvm/</guid><description>&lt;h1 id="类加载与字节码"&gt;类加载与字节码&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-21674835"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-21674835" style="display:none;"&gt;
- 类加载与字节码
- 类加载机制
- 类的生命周期
- 流程
- Loading（加载）
- 把 .class 的字节流变成 JVM 内部的 Class 对象
- 验证
- 确保字节码符合 JVM 规范、类型安全、不会破坏虚拟机
- 准备
- 为类变量 static 字段分配内存并设置默认零值
- 解析
- 把常量池中的符号引用（字符串/描述符）转换成直接引用（指针/句柄）
- 初始化
- 执行类初始化方法 &amp;lt;clinit&amp;gt;
- 准备阶段：有 static 变量的空间&amp;#43;默认值，初始化阶段执行你的 static 赋值逻辑
- 准备阶段 vs 初始化阶段
- 默认值发生在准备阶段
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/19/20260219205053393.png,301,57)
- 显式赋值发生在初始化阶段
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/19/20260219205149229.png,283,54)
- 常量折叠
- &amp;lt;clinit&amp;gt; 类初始化方法
- &amp;lt;clinit&amp;gt; 是编译器合成的类初始化方法
- 来自于所有 static 字段的显式赋值语句和所有 static{} 块
- 按源码出现顺序合并执行
- JVM 保证同一个类的 &amp;lt;clinit&amp;gt; 在多线程下只会被一个线程执行，其他线程阻塞等待
- 初始化触发时机
- 初始化只在主动使用时触发
- 常用触发初始化场景
- new A()
- 读取写入 A.staticField
- 调用 A.staticMethod()
- Class.forName(&amp;#34;A&amp;#34;)
- 初始化子类前，先初始化父类
- JVM 启动时执行 main 类
- Class.forName 和 ClassLoader.loadClass
- Class.forName(&amp;#34;A&amp;#34;)：默认加载&amp;#43;连接&amp;#43;初始化
- ClassLoader.loadClass(&amp;#34;A&amp;#34;)，默认只做加载和必要时连接，不初始化
- 类加载体系与双亲委派
- 三类内置类加载器
- Bootstrap：加载核心库(rt/基础模块)，通常由 JVM 实现，不是 Java 对象
- Platform(JDK9&amp;#43;)/Ext(JDK8)：平台扩展库
- App：应用 classpath 下的类（写的业务代码）
- 双亲委派模型
- 流程
- loadClass(name) 先检查本加载器是否已经加载
- 没加载则委派给父加载器
- 父加载器加载不到，再由当前加载器尝试 findClass 加载
- 最终返回 Class&amp;lt;?&amp;gt;
- 动机
- 安全：防止核心类被伪造替换
- 一致性：保证同名类在 JVM 中尽量只加载一份，避免类型混乱
- 破坏双亲委派
- 破坏方式
- 自定义 ClassLoader，重写 loadClass，不再先委派
- 使用线程上下文类加载器绕过父类限制
- 典型场景
- SPI/ServierLoader：核心库需要加载应用层实现（反向委派）
- JDBC Driver：早期通过 Class.forName 强制注册类驱动
- 应用容器（Tomcat）：每个 WebApp 一个 ClassLoader，实现隔离与热部署
- 破坏带来的风险
- 类重复加载
- 内存泄露：容器热部署时，类加载器无法回收-&amp;gt;元空间泄露
- 类相等判断
- JVM 中，一个类的唯一性由（类的全限定名）&amp;#43;（加载它的 ClassLoader）共同决定
&lt;/textarea&gt;
&lt;h1 id="jvm-内存结构与对象模型"&gt;JVM 内存结构与对象模型&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-51768432"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-51768432" style="display:none;"&gt;
- JVM 内存结构与对象模型
- 运行时数据区
- 整体结构
- 线程私有
- 程序计数器
- Java 虚拟机栈
- 本地方法栈
- 线程共享
- 堆
- 方法区（Hotspot 对应元空间 &amp;#43; 运行时常量池）
- 程序计数器
- 存放线程正在执行的字节码指令地址
- 线程私有：线程切换后能恢复执行位置
- 几乎不会 OOM
- 如果执行 Native 方法，PC 值不一定有意义
- Java 虚拟机栈
- 栈帧结构：一次方法调用对应一个栈帧
- 局部变量表：基本类型值、对象引用、returnAddress
- 操作数栈：字节码执行的临时计算区
- 动态链接：指向运行时常量池的方法/字段引用信息
- 方法返回地址
- 异常表
- 栈中存的对象
- 栈里一般不存对象本体，存的是对象引用
- 对象本体在堆上
- 栈相关错误
- StackOverflowError：递归太深/栈帧太大
- OutOfMemoryError：unable to create new native thread：系统资源耗尽
- 常见参数
- -Xss：每个线程的栈大小（越大单线程可递归更深，但是可创建线程数更少）
- 本地方法栈
- 执行 JNI/Native 方法用
- 堆
- 存放几乎所有对象实例 &amp;#43; 数组
- 常见结构
- 新生代：Eden &amp;#43; Survivor(S0/S1)
- 老年代
- G1 下是 Region 化，不强行分固定代区
- 常用参数
- -Xms 初始堆
- -Xmx 最大堆
- -XX:NewRatio/-XX:SurvivorRatio （传统分代）
- -XX:&amp;#43;UseG1GC 选择收集器
- 堆相关 OOM
- OutOfMemoryError: Java heap space 最常见
- OutOfMemoryError: GC overhead limit exceed
- 方法区/元空间
- 存放类元数据：类结构信息、字段/方法信息、运行时常量池、方法字节码元信息等
- JDK 8 以后 HotSpot 用 Metaspace （本地内存）替代永久代 PermGen
- 常见参数
- -XX:MaxMetaspaceSize 限制元空间上限
- -XX:MetaspaceSize 触发元空间 GC 的阈值
- 典型 OOM
- 动态生成大量类 CGLIB、ByteBuddy、JSP 编译、反射代理
- 热部署/自定义类加载器泄露导致旧类加载器无法回收
- 对象创建与内存分配
- 对象创建的典型步骤
- 类检查：该类是否加载、解析、初始化
- 分配内存：在堆上为对象划分出一块连续空间
- 零值初始化：对象实例字段设置为默认值
- 设置对象头：写入 Mark Word，类指针等元信息
- 执行 &amp;lt;init&amp;gt;：构造方法把字段设置为期望的初始值
- 内存分配方式：指针碰撞 vs 空闲列表
- 指针碰撞
- 堆内存规整，已用区和闲置区是连续的，只要把指针往后移
- 优点：速度快
- 空闲列表
- 堆存在碎片，需要维护空闲列表，从中找合适块
- 优点：有碎片（标记-清除类收集器）
- 是否能用指针碰撞，取决于 GC 是否会整理/复制使空间规整；不规整举要用空闲列表
- 并发分配：TLAB(Thread Local Allocation Buffer)
- TLAB：为每个线程在 Eden 预留一小块私有分配区域，线程在 TLAB 内分配对象时基本无需加锁。
- 原因
- 多线程并发 new 对象频繁，每次都竞争全局堆指针，会有锁/原子操作开销
- TLAB 把大多数分配变成线程本地，提升吞吐
- TLAB 不够时会申请新的 TLAB 或者退到全局分配路径
- 对象内存布局与引用类型
- 对象内存布局
- 对象头
- MarkWord：哈希码、GC 分代年龄、锁信息
- Klass Pointer：指向类元数据（对象属于哪个类）
- 实例数据：定义的字段
- 对齐填充：按 8 字节对齐
- 引用类型
- 强引用
- 默认引用：只要强引用存在，对象不回收
- OOM 大户：强引用过长导致对象一直存活
- 软引用
- 内存不足时才回收
- 弱引用
- 下一次 GC 旧可能回收（只要没有强引用）
- 典型：WeakHashMap、ThreadLocal 的 key
- 虚引用
- 不能通过它获取对象
- 用于对象回收后的通知/资源清理配合 ReferenceQueue
- ThreadLocal 为什么会内存泄露
- 关键结构
- 每个 Thread 内部有一个 ThreadLocalMap
- ThreadLocalMap 的 Key 是 ThreadLocal 的弱引用
- value 是强引用
- 泄露发生条件
- ThreadLocal 实例没有强引用
- 线程还活着
- ThreadLocalMap 里面出现 key=null，value=大对象的 entry
- value 可能长期挂挂着
- 正确姿势
- 用完必须 remove()
- 线程池环境更好要 remove
&lt;/textarea&gt;
&lt;h1 id="gc-体系"&gt;GC 体系&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-63852174"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-63852174" style="display:none;"&gt;
- GC 体系
- GC 基础原理
- GC 要解决的问题
- Java 对象生命周期动态、数量大、手动 free 易错
- GC 的核心目标：在可接受停顿和吞吐下，回收不可达对象占用的内存
- 判定垃圾
- 引用计数
- 优点：实现直接
- 缺点：循环引用难处理
- Hotspot 主流不靠它做 GC 判定
- 可达性分析
- 从 GC Roots 出发做图遍历，遍历不到的对象是垃圾
- GC Roots 常见来源
- 虚拟机栈（栈帧局部变量表）引用的对象
- 方法区/元空间中静态字段引用的对象
- 方法区中常量引用的对象
- JNI 引用的对象
- 活跃线程对象，同步锁持有的对象
- 三大经典回收算法
- 标记-清除（Mark-Sweep）
- 过程：标记可达 -&amp;gt; 清掉不可达
- 优点：简单
- 缺点：内存碎片
- 标记-整理（Mark-Compact）
- 过程：标记可达-&amp;gt;把存活对象向一侧移动-&amp;gt;整理出连续空闲区
- 优点：无碎片
- 缺点：移动对象成本高，停顿更长
- 复制
- 过程：把存活对象复制到另一块区域，清空原区域
- 优点：分配快、碎片少
- 缺点：需要额外空间
- 分代为什么有效
- 核心假设：大多数对象朝生夕死，少数对象存活很久
- 堆分为
- 新生代(Young)：频繁回收，复制算法为主
- 老年代(Old)：存活对象多，回收频率低，整理/并发标记策略为主
- STW 从哪里来
- STW 的根源
- 根枚举/安全点：要一致地拿到 GC Roots，需要让线程在安全点停下
- 对象移动/引用更新：复制/整理需要更新指针，必须在一致视图下完成
- 并发标记时代的写屏障/记忆集
- 当 GC 和应用线程并发时，会出现对象引用关系变化的一致性问题。需要
- 写屏障：在写入引用字段时执行一小段额外逻辑
- Card Table/Remembered Set (RSet)：记录跨代引用，避免全堆扫描
- 为避免扫描整个老年代/全堆，新生代回收时需要知道老年代指向新生代的引用；并发标记需要处理引用更新，因此用写屏障把变更记录到卡表/RSet，保证正确性与效率
- 收集器体系
- G1 (Garbage First)
- 设计目标
- 在吞吐与低停顿之间折中，提供可预测停顿
- 关键机制
- Region 化：堆切成很多等大小 Region
- 按收益优先回收：优先回收“垃圾最多、收益最高“的 Region
- RSet：每个 Region 维护“谁引用我“的集合，避免全堆扫描
- 并发标记：找出老年代可回收对象，随后 Mixed GC 回收部分 old Regions
- G1 的堆结构
- Region 是物理切片；在不同时间扮演不同角色
- Eden Region
- Survivor Region
- Old Region
- Humongous Region (大对象)
- G1 不是固定的新生代/老年代，而是逻辑分代
- G1 的回收类型与流程
- Young GC（仅回收 Young）
- 触发常见原因：Eden 满
- 步骤
- STW 暂停
- 处理根与 RSet，找出可达对象
- 复制存活对象：Eden/Survivor -&amp;gt; 新的 Survivor/Old （晋升）
- 更新引用，释放旧 Eden/Survivor Region
- 恢复执行
- Concurrent Mark （并发标记周期）
- 找出 Old 区的存活情况，为 Mixed GC 提供候选 Old Regions
- 阶段
- Initial Mark：STW，很短，标记 GC Roots 直接可达（通常搭车在一次 Young GC 上）
- Concurrent Mark（并发标记）：与应用并发，从根开始遍历可达视图
- Remark（再标记）：STW，修正并发期间引用变化
- Cleanup（清理/回收候选统计）：统计每个 Region 的存活率，为 Mixed 选择回收集合
- Mixed GC （回收 Young &amp;#43; 部分 Old）
- 触发条件：完成并发标记后，G1 会在若干 GC 中选择一批 Old Regions 与 Young 一起回收
- 特点
- Mixed 仍然是 STW（复制/整理 &amp;#43; 引用更新）
- 部分 Old 由 G1 按收益挑选
- G1 的 Old 回收不是一次性全回收，而是多次 Mixed 逐步回收
- Full GC（G1 不想发生，但会发生）
- Full GC 是兜底
- 并发标记跟不上分配速度
- 晋升失败/分配失败
- to-space exhausted（复制目标空间不足）
- Humongous 压力导致碎片/可用 Region 不够
- Full GC 特点
- STW 时间长
- 会做全堆标记整理
- Humongous（大对象）
- 在 G1 中，大对象通常定义为：大小 &amp;gt; Region 大小的 50%
- 表现
- 大对象会直接分配到 Humongous Regions
- Humongous 回收不如普通对象灵活，容易造成
- Old 快速膨胀
- Mixed 效果变差
- 频繁并发标记或 Full GC 风险上升
- RSet
- 作用：支持只回收一部分 Region 仍然能正确找到 Roots
- 代价：维护 RSet 需要写屏障与额外内存/CPU
- G1 调优
- 4 个参数
- -XX:&amp;#43;UseG1GC
- -XX:MaxGCPauseMillis=&amp;lt;N&amp;gt;: 停顿目标
- -Xms/-Xmx: 堆大小
- -XX:InitiatingHeapOccupancyPercent=&amp;lt;N&amp;gt;：老年代占用多少开始并发标记
- CMS
- CMS 是什么
- 是 HotSpot 里老年代并发收集器
- 核心目标是降低老年代回收的停顿时间-让标记尽可能与应用线程并发执行
- Young 通常配 ParNew
- CMS 总体结构
- 经典周期
- Initial Mark（初始标记）-STW
- Concurrent Mark（并发标记）- 并发
- Remark（重新标记）-STW
- Concurrent Sweep （并发清除）
- Initial Mark（初始标记，STW）
- 从 GC Roots 触发，标记直接可达的对象
- STW：一致性地枚举根，必须在 safepoint 停住线程
- Concurrent Mark（并发标记）
- 从初始标记得到的对象开始，遍历对象图，标记所有可达对象
- 与应用线程并发执行
- 问题：应用线程在改引用-&amp;gt;可能影响标记正确性
- Remark（重新标记，STW）
- 修正并发标记期间因为引用变化导致的“漏标/标记不完整“
- 处理并发期间记录到变更（dirty card 等），把可达闭包补齐）
- STW 原因：并发期间引用一直在变，如果不暂停，就无法在一个确定点收束标记结果
- Concurrent Sweep（并发清除）
- 回收未标记对象的空间，把它们加入空闲链表
- 与应用线程并发执行
- 设计缺陷
- 内存碎片
- CMS 是 Mark-Sweep，不做压缩整理
- 老年代空闲空间可能变成很多不连续小块
- 后果：触发 Full GC（Serial Old/Parallel Old）来做一次标记-整理压缩
- 浮动垃圾
- CMS 标记是并发进行的
- 并发标记开始后，应用线程还在不断创建新对象，产生新垃圾
- 这些对象可能在本轮标记后才产生，不会被本轮回收
- CMS 回收触发的典型机制
- 不会等老年代完全满才开始
- 老年代使用率达到阈值开始一次 CMS 周期 -XX:CMSInitiatingOccupancyFraction 配合 -XX:&amp;#43;UseCMSInitiatingOccupancyOnly
- 如果阈值太高，容易并发失败；阈值太低，吞吐下降
- 常用参数
- -XX:&amp;#43;UseConcMarkSweepGC：启用 CMS
- -XX:&amp;#43;UseParNewGC：新生代用 ParNew
- -XX:&amp;#43;CMSParallelRemarkEnabled: Remark 并行化
- ParNew 回收
- 作用范围：新生代（Eden&amp;#43;S0&amp;#43;S1）
- 算法：标记复制
- 并发：STW，但是 GC 线程并行执行
- 核心流程
- STW（停顿）
- 所有应用线程到达 safepoint 暂停，保证 Roots 枚举一致
- 根扫描（Roots）
- 扫各线程栈、静态引用等 GC Roots
- 额外要处理老年代 -&amp;gt; 新生代的引用
- YoungGC 不能扫描整个老年代，需要靠卡表 Card Table 记录老年代那些卡页变脏，只扫描这些变脏区域找到 Old-&amp;gt;Young 的引用作为额外 Roots
- 复制存活对象
- Eden 中存活对象复制到 Survivor（或者晋升到 Old）
- Survivor（From）中存活对象复制到另一个 Survivor，年龄 &amp;#43; 1
- 复制过程中会设置 forwarding pointer，避免重复复制并更新引用
- 交换 Survivor
- Eden 和旧 From Survior 清空
- To Survivor 变成新的 From
- 恢复引用线程
- 对象如何从 Young 晋升到 Old
- 年龄阈值：对象每熬过一次 YoungGC 年龄 &amp;#43;1，达到阈值 -XX:MaxTenuringThreshold 晋升
- Survivor 放不下：Survivor 容量不足，直接晋升到 old
- 大对象直接进老年代：如果开启/配置了相关策略，大对象可能绕过 Young
- ZGC
- 目标：极低停顿
- 核心：并发标记&amp;#43;并发重定位，依赖读写屏障等机制
- 适合：大堆、强延迟敏感
&lt;/textarea&gt;
&lt;h1 id="jit-与性能"&gt;JIT 与性能&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-84123576"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-84123576" style="display:none;"&gt;
- JIT 与性能
- 解释器 vs JIT
- 解释器：启动快、编译开销低，但是每次执行都要逐条解释代码，长期运行慢
- JIT 编译器：把热点方法编译成机器码，长期运行快，但是编译有成本
- 热点探测
- HotSpot 用计数器发现热点
- 方法调用计数器：方法被多次调用 -&amp;gt; 变热
- 会变计数器：循环回跳次数多-&amp;gt;循环热点
- 分层编译
- 解释执行（Tier 0）：冷启动，收集部分 profile
- C1（Client Compiler，Tier 1-3）：编译快，优化较轻，适合尽快提升性能并继续收集 profile
- C2（Server Compiler，Tier 4）：编译慢，优化重，最终追求极致性能
&lt;/textarea&gt;
&lt;h1 id="线上排障与工具链"&gt;线上排障与工具链&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-32145876"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-32145876" style="display:none;"&gt;
- 线上排障与工具链
- 保存现场
- 采集 4 件套
- GC 日志
- 线程栈 jstack
- Heap Dump jcmd GC.heap.dump 或 jmap -dump
- 关键进程信息：jcmd VM.flags、VM.command_line
- 判断 CPU、内存、锁、I/O
- CPU 高：先看线程在干什么
- RT 抖动：优先看 STW &amp;#43; 锁阻塞 &amp;#43; IO 等待
- 内存涨：看堆/元空间/直接内存 &amp;#43; 是否泄露
- Full GC：看触发原因、晋升、存活率
- OOM：先 dump，再分析引用链
- jps：快速找到 Java 进程
- 列出 Java 进程 PID，主类/jar 名称
- jps -l：显示主类全名或 jar
- 定位 PID 的第一步，用于后续 jcmd/jstack/jstat
- jcmd：万能入口
- jcmd 是 Hotspot 的控制台
- jcmd &amp;lt;pid&amp;gt; VM.version：JDK/Hotspot 版本
- jcmd &amp;lt;pid&amp;gt; VM.command_line: 启动参数
- jcmd &amp;lt;pid&amp;gt; GC.heap_info: 堆概要
- jcmd &amp;lt;pid&amp;gt; GC.class_histogram: 类直方图
- jcmd &amp;lt;pid&amp;gt; GC.heap_dump xx.hprof 生成 heap dump
- jcmd &amp;lt;pid&amp;gt; Thread.print: 线程栈
- jcmd &amp;lt;pid&amp;gt; PerfCounter.print: 性能计数器
- jstack: 线程栈、死锁、阻塞、热点定位
- 用途
- CPU 高：找 RUNNABLE 热点线程在跑啥
- RT 抖动：看 BLOCKED/WAITING/TIMED_WAITING
- 死锁：Found one Java-level deadlock 直接给出环
- 间隔 5-10s 连续采样 3 次，看热点栈是否稳定
- jstat: 快速查看 GC/类加载/编译统计
- 用户
- 判断 GC 是否频繁、老年代是否持续增长、晋升是否异常
- 看 metaspace/class load 是否异常增长
- 常用命令
- jstat -gcutil &amp;lt;pid&amp;gt; 1s 20: 每秒输出 20 次 GC 利用率
- jstat -gc &amp;lt;pid&amp;gt; 1s 20: 更详细的各区大小/容量
- jstat -class &amp;lt;pid&amp;gt; 1s 20: 类加载数量
- jstat -compiler &amp;lt;pid&amp;gt; 1s 20: JIT 编译情况
- jmap vs jcmd heap_dump: dump 与直方图
- 直方图
- 快速看到哪个类对象最多/占用最大
- 常用于 OOM 前后或内存上涨时的第一眼判断
- heap dump
- 确定谁在引用链上把对象留住
- Arthas: 线上动态诊断
- dashboard: CPU/内存/线程/GC 全览
- thread -n 5: 找 CPU 占用最高的线程
- thread &amp;lt;id&amp;gt;: 看指定线程栈
- jvm: JVM 信息、参数、内存、GC、类加载器
- heapdump xx.hprof: 导出 heap dump
- trace com.xx.Service method: 方法调用链耗时
- watch com.xx.Service method &amp;#39;{params,returnObj,throwExp}&amp;#39; -x 2: 观察入参/返回/异常
- tt：录制与回放
- 高频场景
- CPU 100%
- 目标：找到哪个线程在烧 CPU，以及烧 CPU 的代码路径
- 标准流程：
- OS 层确认
- top/htop 看进程 CPU
- 定位 java 线程
- Arthas: thread -n 5
- top -H -p &amp;lt;pid&amp;gt; 找到线程 TID
- 将 TID 转 16 进制
- printf &amp;#34;%x\n&amp;#34; 12345
- jstack &amp;lt;pid&amp;gt; 或 jcmd Thread.print 中搜索 16 进制 nid
- 找到对应线程栈
- 判断栈顶
- 如果是业务循环/正则灾难/大 JSON 解析/排序/加密-&amp;gt;代码问题
- 如果是 GC 线程占 CPU -&amp;gt; 看 GC 是否频繁
- 需要更准时，上 profiler
- JFR/async-profiler 火焰图确认热点
- RT 抖动/毛刺
- 目标：分辨是 STW、锁阻塞、还是 IO/下游慢
- 标准流程
- 先看是否有 STW
- GC 日志看 pause
- jstat -gcutil 看 GC 是否在尖峰时频繁
- 非 GC，查看线程状态分布
- jstack 多次采样
- BLOCKED 多：锁竞争
- WAITING/TIME_WAITING 多：可能在等 IO/队列/park
- 下游排查
- DB 慢查询、线程池队列积压，连接池耗尽
- 频繁 Full GC
- 目标：解释为什么 Full 以及回收效果如何
- 标准流程：
- 看 GC 日志：Full GC 的触发原因
- jstat -gcutil 看
- Old 是否增长
- FGC 是否增长
- YGC 后 Old 增长是否很快
- 先做直方图
- jcmd GC.class_histogram 看大对象/大集合
- 必要时 heap dump:
- MAT 看 Dominator Tree &amp;#43; Path to Roots
&lt;/textarea&gt;</description></item><item><title>RocketMQ 复习</title><link>https://blog.ans20xx.com/posts/backend/rocketmq-%E5%AD%A6%E4%B9%A0/</link><pubDate>Wed, 18 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.ans20xx.com/posts/backend/rocketmq-%E5%AD%A6%E4%B9%A0/</guid><description>&lt;h1 id="概述"&gt;概述&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-38471562"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-38471562" style="display:none;"&gt;
- 概述
- 定位
- RocketMQ 是一个分布式消息中间件，用于在分布式系统中以异步方式传递时间/数据
- 典型问题
- 异步解耦
- 场景：下单后做扣库存、发优惠券、发短信、写审计日志
- 没用 MQ：下单接口串行调用 N 个下游 -&amp;gt; 延迟高、耦合重、下游挂了全挂
- 用 MQ：下单服务只负责写订单 &amp;#43; 发消息，下游异步消费，链路变短、隔离故障、便于扩展
- 削峰填谷
- 场景：大促瞬时 QPS 飙升，下游撑不住
- 用 MQ：把瞬时峰值写入 MQ，消费者按能力稳定拉取 -&amp;gt; 系统不被打爆
- 广播/集群消费
- 广播：配置刷新、缓存预热、全量通知
- 集群：订单履约、发货、对账（同组只要处理一次）
- 顺序/延迟/事务
- 顺序：同一订单状态流转必须按照顺序处理
- 延迟：30 分钟未支付自动关单
- 事务：下单写库 &amp;#43; 发消息要一致性
- 领域模型
- Topic/MessageQueue（并发与顺序的载体）
- Topic：逻辑分类
- MessageQueue（队列/分区）：Topic 下的物理分片
- 并发的本质：多个队列=&amp;gt;多个消费者实例并行消费
- 顺序的边界：只保证同一队列内有序，跨队列无需
- 原因：MessageQueue 带来了水平扩展吞吐（分片并行）&amp;#43;提供队列内顺序语义
- Producer 生产者
- 生产者发送消息时，核心要决定：这条消息进入哪个队列
- 普通消息：轮询/负载均衡选择队列（提升吞吐）
- 顺序消息：按业务 key 做一致性路由到同一队列，保证顺序
- ConsumerGroup 消费组
- 消费组：一组消费者实例共享同一套消费进度
- 集群消费：同组内分摊队列
- 广播消费：每个实例都处理全量消息
- 同一个 Topic，两个不同的 ConsumerGroup 相当于两套独立的订阅者，各自消费一份完整消息流
- 卖点
- 高吞吐
- 顺序写：消息落到 CommitLog -&amp;gt; 磁盘友好
- 零拷贝/高效网络传输：减少内核态/用户态拷贝开销
- 批量与异步：批量写入、批量拉取、异步刷盘/复制
- 读写分离：写走 CommitLog，读通过索引定位
- 可扩展
- Topic 多队列 -&amp;gt; 消费端扩容实例可提升并发
- Broker 集群扩容 -&amp;gt; 分摊存储与 I/O 压力
- NameServer 做路由发现 -&amp;gt; Producer/Consumer 动态感知集群变化
- 靠队列分片 &amp;#43; Broker 横向扩展 &amp;#43; 消费组负载均衡
- 语义能力
- 至少一次投递与重复消费
- RocketMQ 工程实践通常是至少一次：可能重复，但不丢
- 业务必须幂等
- 重试与死信
- 消费失败 -&amp;gt; 重试
- 达到最大次数仍然失败 -&amp;gt; 进入死信队列
- 生产实践：DLQ 监控高进 &amp;#43; 人工/自动补偿 &amp;#43; 重新投递
- 事务消息（最终一致性）
- 目标：本地事务与消息发送一致
- 核心机制：半消息（先存 broker 不投递）-&amp;gt; 本地事务 -&amp;gt; commit 才投递；不确定则回查
- 它解决的是最终一致性，不是强一致 ACID
&lt;/textarea&gt;
&lt;h1 id="核心概念"&gt;核心概念&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-51476823"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-51476823" style="display:none;"&gt;
- 核心概念
- Topic
- 定义：消息的逻辑分类，Producer 发送到 Topic，Consumer 订阅 Topic。
- 关键点：Topic 本身不决定并发，并发来自 Topic 下的 MessageQueue 数量
- 一个 Topic 通常会在多个 Broker 上分布存储，每个 Broker 上持有一些队列分片
- MessageQueue
- 定义：Topic 的物理分片，是投递与顺序的基本单元。
- 并发能力=队列数上限：同哦个 ConsumerGroup 里，一个队列同一个时刻只会被一个消费者实例消费
- 顺序边界：RocketMQ 的顺序语义通常是队列内有序，跨队列不保证
- 路由载体：Producer 发送时必须选择一个队列（轮询/哈希/自定义）
- Producer 生产者
- 职责：构造消息、选择 Topic 队列、发送、处理失败重试
- Producer 会先拿到 Topic 的路由信息（队列分布在哪些 Broker）
- 发送失败通常会重试/换 Broker（取决于客户端策略与路由状态）
- 发送模式
- 同步：拿到 sendResult 才返回（最常用）
- 异步：回调（高吞吐/低延迟）
- 单向：不关心结果（日志/打点）
- Consumer（消费者）
- 职责：拉取消息（Pull 体系，Push 是封装）、本地处理、成功后提交 offset，失败触发重试
- RocketMQ PushConsumer 本质是客户端内部循环 Pull &amp;#43; 回调你业务代码
- 消费并发：由消费线程数 &amp;#43; 队列分配决定
- ConsumerGroup（消费组）
- 定义：一组消费者实例，共享同一套消费进度（offset），一起消费 Topic。
- CLUSTERING（集群消费）：同组分摊消费（每条只处理一次）
- BROADCASTING（广播消费）：每个实例都处理全量（各自进度）
- NameServer
- 定位：路由注册与发现（轻量注册中心，不像 Kafka 把协调状态放在上面）
- Broker 启动后像 NameServer 注册：Topic -&amp;gt; Broker -&amp;gt; 队列信息
- Producer/Consumer 定期从 NameServer 拉取最新路由
- NameServer 挂了是否可以发消息
- 短期通常能发（客户端还有路由缓存），但路由无法更新；长期不行，且新 Topic/新 Broker 不可见。
- Broker
- 职责：接收消息，持久化存储，为消费者提供拉取、维护消费进度相关能力、做复制。
- 消息一般先写 CommitLog （顺序写），再构建队列索引。
- 吞吐高的原因：顺序写&amp;#43;批量&amp;#43;高效 IO 路径
- Offset （消费进度）
- 定义：ConsumerGroup 在每个 MessageQueue 上的消费位置
- 成功消费 -&amp;gt; 提交 offset
- 失败 -&amp;gt; 不提交 -&amp;gt; 后续重试/再次提交
- 数据流
- 生产链路
- 拿路由
- Producer 启动后从 NameServer 拉取 Topic 路由：有哪些 Broker、每个 Broker 上有哪些队列
- 选队列
- Produer 根据策略选择一个 MessageQueue
- 普通消息：轮询/负载均衡
- 顺序消息：按业务 key 哈希到固定队列
- 发送到 Broker
- Producer 把消息发送到队列所在 Broker
- 持久化与应答
- Broker 持久化成功后返回 sendResult，Producer 认为发送成功
- 消费链路
- 订阅与分配
- Consumer 加入 ConumserGroup，订阅 Topic
- 在集群模式下，同组实例会进行队列分配：每个 MessageQueue 分配给某个实例
- 拉取
- 被分配到队列的 Consumer 维护该队列的 offset，向 Broker 发起拉取请求
- 拉取这个队列从 offset=N 开始的下一批消息
- 本地处理
- Consumer 收到消息后调用你的业务处理逻辑（线程池并发执行）
- 提交 Offset
- 业务成功 -&amp;gt; 提交 offset（推进进度）
&lt;/textarea&gt;
&lt;h1 id="可靠性语义幂等重试死信"&gt;可靠性、语义、幂等、重试、死信&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-51278364"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-51278364" style="display:none;"&gt;
- 可靠性/语义/幂等/重试/死信
- 消息语义
- 三种语义
- At-most-once（至多一次）：不重复，但可能丢
- At-least-once（至少一次）：尽量不丢，但可能重复（RocketMQ 采用）
- Exactly-once（恰好一次）：不丢不重
- 为什么会重复
- 本质：分布式不确定性，无法在网络超时常见里判断对方是否已经成功
- 生产端导致重复：超时不等于失败
- Producer 发出了 send 请求
- Broker 实际写成功了，但 ack 在路上丢了/Producer 超时
- Producer 认为失败 -&amp;gt; 重试发送
- Broker 上存在两条业务等价消息
- 消费端导致重复：处理成功但提交 offset 失败/延迟
- Consumer 拉到消息并执行成功
- 在提交 offset 前发生进程重启/网络抖动/提交失败
- 下次启动从旧 offset 拉 -&amp;gt; 同一消息再次被处理
- 重试与死信：失败后到底怎么流转
- 消费失败 -&amp;gt; 重试：两类消费方式的区别
- 并发消费：消息可并行处理；失败后会触发稍后重投
- 顺序消费：同队列内严格顺序；失败会卡住队列，知道成功或打到策略
- 重试次数与延迟
- 消费失败后，消息会进入“重试主题/重试队列“
- 每次重试都会有一定的延迟
- 达到最大重试次数后仍然失败 -&amp;gt; 进入死信队列
- DLQ 常见命名 %DLQ%&amp;lt;ConsumerGroup&amp;gt;
- DLQ 工程治理
- 监控报警
- DLQ 堆积量、进入 DLQ 的速率、重试次数分布、消费失败率
- 分类原因
- 可重试：下游超时、偶发网络、锁冲突
- 不可重试：参数非法、业务校验失败、数据缺失
- Offset 管理
- Offset 是什么
- Offset 是 ConsumerGroup 在每个 MessageQueue 上的消费位置
- 不是全局一个值，而是 (group,topic,queueId) -&amp;gt; offset
- 正确提交时机
- 处理成功后再提交
&lt;/textarea&gt;
&lt;h1 id="性能与扩展顺序与分区堆积治理"&gt;性能与扩展、顺序与分区、堆积治理&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-53418627"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-53418627" style="display:none;"&gt;
- 性能与扩展、顺序与分区、堆积治理
- 吞吐与并发怎么调
- 吞吐链路拆解，多个环节的 min
- Producer 发送能力
- Broker 写入能力
- Consumer 拉取与处理能力
- 队列分片与分配：队列数决定
- 并发的硬上限：Queue 数决定可扩展性
- 集群消费模式下，同一个 ConsumerGroup 内，一个 MessageQueue 同一时刻只会分配给一个消费者实例
- 有效消费实例并行度 &amp;lt;= 队列数 Q
- 有 C 个实例：有效利用 = min(Q,C)
- 线程数只能提高单实例处理并行，队列数太少时，扩容实例不涨吞吐
- 调整的三类杠杆
- 队列数
- 现象：加消费者实例吞吐不涨/涨很少
- 原因：Q 太小，很多实例分不到队列
- 操作：提高 Topic 队列数
- 消费者实例数
- 前提：Q 足够
- 收益：线性提升
- 消费者线程数/批量参数
- 适用 Q 足够但实例受限或业务处理轻的场景
- 堆积定位
- 堆积的数学本质
- 堆积增长 = 生产速率 &amp;gt; 消费速率，差值积累在 broker
- 堆积原因分类
- 消费变慢
- 失败重试太多
- 生产突增
- Broker 压力抖动
- 堆积排查步骤
- 确定堆积是全局还是局部
- 如果是局部队列对接，大概率是热点 key/顺序路由/某实例异常
- 消费失败率与重试量
- 失败率高 -&amp;gt; 先止血，否则越修越崩
- 识别同一类异常栈是否集中
- 看单消息处理耗时分布
- 平均不重要，重点看 p95/p99
- p99 高往往来自：慢 SQL、远程调用尾延迟、锁竞争
- 看下游容量
- 看队列数与分配
- 看 Broker 层资源
- 堆积治理手段
- 止血
- 限流生产端：控制入口
- 失败隔离：把异常消息旁路到补偿 Topic/DLQ，避免拖垮主消费
- 降级业务逻辑：先做核心动作，其他异步/延后
- 恢复吞吐
- 扩容消费者实例
- 提高批量拉取/批量处理
- 优化热点：把热点 key 拆散
- 若某些队列极端堆积：考虑迁移/重新分配队列所在 broker
- 根治
- 优化热点：慢 SQL、缓存、异步化、批处理
- 调整队列数与 key 策略
- 建立 DLQ/补偿闭环与告警
- 压测与容量规划：明确峰值可承受的 TPS 与延迟
&lt;/textarea&gt;
&lt;h1 id="存储与高可用"&gt;存储与高可用&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-37461528"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-37461528" style="display:none;"&gt;
- 存储与高可用
- Broker 存储核心
- 三类文件
- CommitLog：消息本体的唯一真相源
- 所有 Topic 的消息都会追加吸入 CommitLog（不是每个队列一个日志，而是统一日志）
- 写入模型是 append-only（追加写），是 RocketMQ 的高吞吐基础之一
- ConsumeQueue：面向消费的队列索引
- 消费者按照 MessageQueue 语义来消费，CommitLog 是全局混合写入的，需要一个队列视角的索引结构把队列映射回 CommitLog
- ConsumeQueue 的本质是：每个 (topic, queueId) 一套逻辑队列索引，记录这条队列消息在 CommitLog 的物理位置等信息
- ConsumeQueue 是可重建的-Broker 异常后基于 CommitLog 恢复并重建缺失的 ConsumeQueue 索引，因此 ConsumeQueue 对绝对不丢要求没那么高
- IndexFile：按 key 查询的哈希索引
- 用于按 message key 做快速查询
- 写入流程
- 流程
- Producer 把消息发送到 Broker
- Broker 顺序追加写 CommitLog
- 再异步/批量地更新 ConsumeQueue/IndexFile
- 原因
- 顺序写 CommitLog 最快
- 索引可以延迟/批量构建，并且 ConsumeQueue 丢了也能重建
- 读取链路
- 消费者按队列 (topic, queueId) 消费
- Consumer 拉取时携带 offset
- Broker 通过 ConsumeQueue 把队列 offset 映射到 CommitLog 的物理位置
- 再从 CommitLog 把消息读出来返回给 Consumer
- 吞吐高：顺序写&amp;#43;mmap&amp;#43;零拷贝&amp;#43;批量
- 顺序写：CommitLog append-only 模式写磁盘
- mmap 内存映射与 MappedFile 抽象
- Broker 用 mmap 把文件映射为虚拟内存，写入像写内存一样，减少传统 read/write 的系统调用与拷贝成本
- 零拷贝方向
- 批量
- 批量写入、批量拉取能显著摊薄网络往返与系统调用开销
- HA：复制、切换、延迟与一致性取舍
- 传统 Master/Slave：复制延迟与一致性
- Master 负责写入，Slave 复制数据做冗余
- 关键变量：复制是同步还是异步，直接决定
- RPO（数据丢失窗口）：异步复制可能丢失最近一段
- RTO（恢复时间）：切换/重连需要时间
- 自动主从切换：Controller
- Controller：把谁是主、怎么选主、怎么切这个协调逻辑集中管理，从而支持自动故障转移
- 切换时关注
- 选主/仲裁机制
- 路由更新
- 复制进度
- 业务容忍度
- RocketMQ-on-DLedger(Raft)：更强一致的 HA 思路
- DLedge 把 CommitLog 的复制变成 Raft 日志复制，从而提高一致性并提供自动选主
- 代价：写路径更重，延迟更高，但一致性更强、切换更自动化
&lt;/textarea&gt;</description></item><item><title>容器类</title><link>https://blog.ans20xx.com/posts/backend/%E5%AE%B9%E5%99%A8/</link><pubDate>Sat, 11 Jan 2025 00:00:00 +0000</pubDate><guid>https://blog.ans20xx.com/posts/backend/%E5%AE%B9%E5%99%A8/</guid><description>关于容器的一些心得体会</description></item></channel></rss>