<?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>JVM on 安橙的博客</title><link>https://blog.ans20xx.com/tags/jvm/</link><description>Recent content in JVM on 安橙的博客</description><generator>Hugo -- 0.163.3</generator><language>zh</language><lastBuildDate>Thu, 19 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.ans20xx.com/tags/jvm/index.xml" rel="self" type="application/rss+xml"/><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></channel></rss>