【032】排查入门:jstack、heap dump、Arthas 初识

本文聚焦 Java 并发编程的「最后一公里」------问题排查与诊断。当你写的代码在测试环境跑得好好的,一到线上就出现各种「疑难杂症」:线程阻塞、内存持续增长、CPU 飙升、响应超时......怎么办?靠猜是不行的,你需要掌握一系列排查工具和方法。本文会从最基础的 jstack 讲起,教你如何分析线程状态和死锁;再介绍 heap dump(堆转储)的获取和分析方法,帮助你定位内存泄漏;最后介绍阿里巴巴开源的诊断神器 Arthas,让你体验「在线调试」的强大能力。读完本文,你将掌握 Java 排查的「三板斧」,面对线上问题时不再手足无措。


1. 为什么排查能力很重要

1.1 线上问题有多棘手

很多同学在开发阶段写得一手好代码,但一到线上就「翻车」:

  • 线程池耗尽,请求堆积
  • 内存持续增长,最终 OOM 崩溃
  • CPU 100%,服务响应缓慢
  • 死锁导致部分功能完全不可用

这些问题往往有以下特点:

  • 难以复现:测试环境正常,线上才出问题
  • 随机性强:同样的代码,有时正常有时异常
  • 影响范围大:线上问题直接影响用户体验和业务

如果没有排查能力,你只能:

  • 重启服务(治标不治本)
  • 加日志、重新部署(影响业务)
  • 干着急(等待「大腿」支援)

1.2 排查的核心思路

Java 排查的核心思路是「看穿表象,找到根因」:

  • 表象:线程卡死、内存爆满、CPU 飙升
  • 根因:死锁、内存泄漏、死循环、锁竞争

排查工具的作用,就是帮你从「表象」一步步追溯到「根因」。

排查三板斧

  1. jstack:看线程在做什么
  2. heap dump:看对象在哪里
  3. Arthas:动态观测和干预

2. jstack:线程分析神器

2.1 jstack 是什么

jstack 是 JDK 自带的线程堆栈分析工具,位于 $JAVA_HOME/bin/jstack。它可以:

  • 打印指定 Java 进程的线程堆栈信息
  • 检测死锁(Deadlock)
  • 分析线程状态(RUNNABLE、BLOCKED、WAITING 等)

2.2 基本用法

获取 Java 进程 PID

首先需要找到目标 Java 进程的 PID:

bash 复制代码
# 方式一:jps 命令
jps -l

# 方式二:ps 命令
ps -ef | grep java

# 方式三:top 命令
top -b -n 1 | grep java

使用 jstack 打印堆栈

bash 复制代码
# 打印线程堆栈(推荐:-l 参数打印更多锁信息)
jstack -l <pid>

# 打印堆栈并输出到文件
jstack -l <pid> > jstack.log

# 强制打印(即使 jstack 被阻塞)
jstack -F -l <pid> > jstack.log

2.3 线程状态解读

jstack 输出的线程堆栈中,每个线程都有状态标记:

状态 含义 常见原因
NEW 新建状态 线程刚创建,还未启动
RUNNABLE 可运行状态 正在 CPU 上运行或等待 CPU 时间片
BLOCKED 阻塞状态 等待获取 monitor 锁
WAITING 等待状态 无限期等待(Object.wait、Lock.await)
TIMED_WAITING 限时等待状态 限时等待(Thread.sleep、Lock.await(timeout))
TERMINATED 终止状态 线程已执行完毕

RUNNABLE 状态

java 复制代码
"Thread-1" #15 prio=5 os_prio=0 tid=0x00007f8a5c01e800 nid=0x7a2c runnable [0x00007f8a5a7e7000]
   java.lang.Thread.State: RUNNABLE
    at com.example.demo.DemoApplication.lambda$main$0(DemoApplication.java:18)
    at com.example.demo.DemoApplication$$Lambda$1/1175965214.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)

RUNNABLE 表示线程正在 CPU 上执行,可能是正常计算,也可能是死循环。

BLOCKED 状态

java 复制代码
"Thread-2" #16 prio=5 os_prio=0 tid=0x00007f8a5c020800 nid=0x7a2d waiting for monitor entry [0x00007f8a5a8f0000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.example.demo.DemoApplication.lambda$main$1(DemoApplication.java:25)
    - waiting to lock <0x00000000d5f8b828> (a java.lang.Object)
    - locked <0x00000000d5f8b818> (a java.lang.Object)

BLOCKED 表示线程在等待获取对象锁(synchronized),常见于锁竞争场景。

WAITING 状态

java 复制代码
"Thread-3" #17 prio=5 os_prio=0 tid=0x00007f8a5c022800 nid=0x7a2e in Object.wait() [0x00007f8a5a9ff000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000d5f8b838> (a java.lang.Object)
    at java.lang.Object.wait(Object.java:502)
    at com.example.demo.DemoApplication.lambda$main$2(DemoApplication.java:32)

WAITING 表示线程在无限期等待,通常是调用了 Object.wait() 或 Lock.await()。

2.4 死锁检测

jstack 可以自动检测死锁(Deadlock),输出中会包含 "Found one Java-level deadlock" 信息:

bash 复制代码
$ jstack -l <pid>

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock object 0x00000000d5f8b828 (a java.lang.Object),
  which is held by "Thread-2"
"Thread-2":
  waiting to lock object 0x00000000d5f8b818 (a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
    at com.example.demo.DemoApplication.lambda$main$0(DemoApplication.java:18)
    - waiting to lock <0x00000000d5f8b828> (a java.lang.Object)
    - locked <0x00000000d5f8b818> (a java.lang.Object)
    ...
"Thread-2":
    at com.example.demo.DemoApplication.lambda$main$1(DemoApplication.java:25)
    - waiting to lock <0x00000000d5f8b818> (a java.lang.Object)
    - locked <0x00000000d5f8b828> (a java.lang.Object)
    ...

死锁示例代码

java 复制代码
public class DeadlockDemo {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();
    
    public static void main(String[] args) {
        // 线程 1:先锁 lock1,再锁 lock2
        new Thread(() -> {
            synchronized (lock1) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                synchronized (lock2) {
                    System.out.println("线程 1 完成");
                }
            }
        }, "Thread-1").start();
        
        // 线程 2:先锁 lock2,再锁 lock1
        new Thread(() -> {
            synchronized (lock2) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                synchronized (lock1) {
                    System.out.println("线程 2 完成");
                }
            }
        }, "Thread-2").start();
    }
}

2.5 实战:分析线程阻塞问题

问题场景:线上服务响应变慢,CPU 使用率不高,但请求堆积。

排查步骤

  1. 查看进程状态
bash 复制代码
# 查看 Java 进程
$ jps -l
12345 com.example.demo.DemoApplication

# 查看进程 CPU 和内存占用
$ top -p 12345
  1. 打印线程堆栈
bash 复制代码
$ jstack -l 12345 > jstack.log
  1. 分析堆栈内容

打开 jstack.log,搜索关键信息:

  • BLOCKED 线程:大量 BLOCKED 线程说明锁竞争激烈
  • WAITING 线程:大量 WAITING 线程说明在等待某些条件
  • 死锁:搜索 "deadlock" 关键字
  • 线程数量:统计 "Thread-" 开头的线程数量
  1. 定位问题代码

根据堆栈信息,定位到具体的代码行:

复制代码
"http-nio-8080-exec-10" #50 daemon prio=5 os_prio=0 tid=0x00007f8a5c0a2800 nid=0x7a45 waiting for monitor entry [0x00007f8a5a9bf000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.example.demo.service.OrderService.getOrder(OrderService.java:45)
    - waiting to lock <0x00000000d5f8b828> (a com.example.demo.service.OrderService)
    - locked <0x00000000d5f8b818> (a com.example.demo.service.OrderService)

从堆栈可以看出:

  • 线程在 OrderService.java:45 行等待获取锁
  • 锁被 OrderService 类的另一个实例持有

常见阻塞原因

  • synchronized 锁竞争:多个线程竞争同一把锁
  • 数据库连接池耗尽:等待从连接池获取连接
  • 外部服务超时:调用外部接口超时
  • 死锁:两个或多个线程相互等待对方释放锁

3. heap dump:内存分析利器

3.1 什么是 heap dump

heap dump(堆转储)是 Java 堆内存的快照,包含了堆中所有对象的信息:对象类型、字段值、引用关系等。通过分析 heap dump,可以找到:

  • 内存泄漏的根本原因
  • 占用内存最多的对象
  • 对象的创建来源(谁创建了这些对象)

3.2 获取 heap dump 的方式

方式一:jmap 命令

bash 复制代码
# 获取 heap dump(推荐:-dump:format=b 表示二进制格式)
jmap -dump:format=b,file=heap.hprof <pid>

# 导出到指定目录
jmap -dump:format=b,file=/tmp/heap.hprof <pid>

方式二:JVM 参数自动导出

在启动参数中添加:

bash 复制代码
# OOM 时自动导出 heap dump
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/tmp/heap.hprof

这样当发生 OOM 时,会自动导出 heap dump。

方式三:jcmd 命令

bash 复制代码
jcmd <pid> GC.heap_dump /tmp/heap.hprof

方式四:JMX / JConsole

通过 JMX 连接 Java 进程,手动触发 heap dump。

3.3 分析 heap dump 的工具

工具一:jhat(已废弃)

jhat 是 JDK 自带的 heap dump 分析工具,但功能有限,已被官方标记为废弃。

bash 复制代码
# 启动 jhat 服务器
jhat heap.hprof
# 访问 http://localhost:7000

工具二:Eclipse MAT(推荐)

Eclipse Memory Analyzer Tool(MAT)是功能最强大的 heap dump 分析工具,完全免费:

工具三:Visual VM

Visual VM 是 JDK 自带的可视化工具:

bash 复制代码
# 启动 Visual VM
jvisualvm

可以加载 heap dump 文件,提供对象视图、OQL 查询等功能。

工具四:JProfiler(商业)

功能强大的商业工具,提供实时监控、CPU 采样、内存分析等功能。

3.4 使用 Eclipse MAT 分析 heap dump

步骤一:打开 heap dump

启动 MAT,打开 heap.hprof 文件。

步骤二:查看 Leak Suspects

MAT 会自动运行 Leak Suspects 分析,报告可能的内存泄漏:

复制代码
Leak Suspects
-------------
1. One instance of "com.example.demo.service.CacheService" 
   loaded by "sun.misc.Launcher$AppClassLoader @ 0x..." 
   occupies 512,345,678 bytes (45.23%) of heap memory.

2. The stack trace of the thread that created this 
   "CacheService" instance:
   at com.example.demo.service.CacheService.<init>
   at com.example.demo.config.CacheConfig.createCache
   ...

步骤三:查看 Histogram

Histogram 视图显示所有类的实例数量和内存占用:

复制代码
Class Name                                    | Objects | Shallow Heap
----------------------------------------------|---------|-------------
java.lang.String                              | 123,456 |   2,967,744
java.util.HashMap$Node                        |  98,765 |   3,160,480
com.example.demo.service.OrderService         |  45,678 |   5,840,784
...

步骤四:查看 Dominator Tree

Dominator Tree 显示哪些对象持有大量内存:

复制代码
Dominator Tree
--------------
Name                                    | Shallow Heap | Retained Heap
----------------------------------------|--------------|---------------
com.example.demo.service.CacheService   |     1,024    |   512,345,678
 |- cache (HashMap)                     |        48    |   512,000,000
    |- key1 -> OrderEntity              |        56    |   100,000,000
    |- key2 -> OrderEntity              |        56    |   100,000,000
    ...

步骤五:使用 OQL 查询

MAT 提供 OQL(Object Query Language)查询:

sql 复制代码
-- 查询大于 10MB 的 byte 数组
SELECT * FROM byte[] WHERE length > 10485760

-- 查询特定类的所有实例
SELECT * FROM com.example.demo.service.OrderService

-- 查询被大量引用的 String
SELECT * FROM java.lang.String WHERE count > 1000

3.5 常见内存问题分析

问题一:内存泄漏

内存泄漏是指对象被错误地持有,导致无法被 GC 回收:

java 复制代码
// 典型内存泄漏:静态集合持有对象引用
public class CacheService {
    private static final Map<String, Object> cache = new HashMap<>();
    
    public void put(String key, Object value) {
        cache.put(key, value);  // 永远不会被清除
    }
}

在 heap dump 中,表现为:对象实例数量持续增长,Retained Heap 很大。

问题二:内存溢出(OOM)

OOM 的常见原因:

  • Heap OOM:对象太多,堆内存不足
  • Metaspace OOM:类太多,元空间不足
  • Stack Overflow:递归调用太深
  • Direct Memory OOM:NIO 使用了太多直接内存

问题三:内存碎片

频繁创建和销毁大对象会导致内存碎片:

java 复制代码
// 频繁创建大对象
for (int i = 0; i < 100000; i++) {
    byte[] buffer = new byte[10 * 1024 * 1024];  // 10MB
    // 处理 buffer
}

在 heap dump 中,表现为:大量 byte[] 对象,内存不连续。


4. Arthas:在线诊断神器

4.1 Arthas 是什么

Arthas(阿尔萨斯)是阿里巴巴开源的 Java 诊断工具,被誉为「Java 界的 Chrome DevTools」。它的特点是:

  • 无需重启:在线.attach 到运行中的 Java 进程
  • 功能丰富:支持方法监控、调用链追踪、热点方法分析等
  • 上手简单:命令行交互式界面

4.2 安装和使用

方式一:下载启动脚本

bash 复制代码
# 下载 arthas-boot.jar
curl -O https://arthas.aliyun.com/arthas-boot.jar

# 启动 Arthas(会列出 Java 进程供选择)
java -jar arthas-boot.jar

方式二:使用 as.sh(Linux/macOS)

bash 复制代码
# 一键安装
curl -L https://arthas.aliyun.com/install.sh | sh

# 启动
./as.sh <pid>

方式三:IDEA 插件

在 IDEA 插件市场搜索 "Arthas",安装后可以右键选择 "Start Arthas"。

4.3 核心功能

功能一:dashboard------实时面板

bash 复制代码
$ dashboard

显示实时数据面板,包括:

  • 线程信息(CPU、状态、堆栈)
  • 内存信息(堆内存、各区域使用量)
  • 运行信息(JVM 版本、启动时间)

功能二:thread------线程分析

bash 复制代码
# 查看所有线程
$ thread

# 查看最忙的 N 个线程
$ thread -n 5

# 查看线程堆栈
$ thread 1

# 查看阻塞的线程
$ thread -b

# 查看死锁
$ thread --state BLOCKED

功能三:watch------方法监控

bash 复制代码
# ��控方法返回值
$ watch com.example.demo.service.OrderService getOrder return-value

# 监控方法入参和返回值
$ watch com.example.demo.service.OrderService getOrder "{params,returnObj}"

# 监控方法执行时间
$ watch com.example.demo.service.OrderService getOrder "{params,returnObj}" -x 3

# 监控异常
$ watch com.example.demo.service.OrderService getOrder "{params,throwExp}" -e

功能四:trace------方法调用链

bash 复制代码
# 追踪方法调用链
$ trace com.example.demo.service.OrderService getOrder

# 跳过 JDK 方法
$ trace com.example.demo.service.OrderService getOrder '#cost > 10'

# 追踪并输出耗时
$ trace com.example.demo.service.OrderService getOrder -n 5

功能五:monitor------方法统计

bash 复制代码
# 统计方法调用次数和耗时
$ monitor -c 5 com.example.demo.service.OrderService getOrder

# 输出:
# timestamp        class                                   method    total  success  fail  avg-rt(ms)  fail-rate
# 2024-01-01 12:00 com.example.demo.service.OrderService  getOrder  100    99       1     12.34       1.00%

功能六:jad------反编译

bash 复制代码
# 反编译类
$ jad com.example.demo.service.OrderService

# 反编译并保存到文件
$ jad com.example.demo.service.OrderService > OrderService.java

功能七:ognl------执行表达式

bash 复制代码
# 查看静态字段
$ ogl @com.example.demo.config.AppConfig@maxRetry

# 调用静态方法
$ ogl @com.example.demo.service.CacheService@getInstance().get("key")

# 修改对象属性
$ ogl @com.example.demo.service.OrderService@order.setStatus("CANCELLED")

功能八:sc------查看类信息

bash 复制代码
# 查看类的详细信息
$ sc -d com.example.demo.service.OrderService

# 查看类的字段
$ sc -f com.example.demo.service.OrderService

功能九:sm------查看方法信息

bash 复制代码
# 查看类的方法
$ sm com.example.demo.service.OrderService

# 查看方法的详细信息
$ sm -d com.example.demo.service.OrderService getOrder

4.4 实战案例

案例一:排查方法执行慢

bash 复制代码
# 追踪方法调用,找出耗时点
$ trace com.example.demo.controller.OrderController createOrder

# 输出:
# `---ts=2024-01-01 12:00:00;thread_name=http-nio-8080-exec-1;---`
# `---[12.3456ms]com.example.demo.service.OrderService:createOrder() #28`
# `    `---[10.1234ms]com.example.demo.service.OrderService:validateOrder() #35`
# `    `---[1.2345ms]com.example.demo.service.OrderService:saveOrder() #42`
# `    `---[0.9876ms]com.example.demo.service.NotificationService:send() #50`

# 从输出可以看出,validateOrder() 耗时最长(10ms)

案例二:排查接口返回 null

bash 复制代码
# 监控方法返回值
$ watch com.example.demo.service.OrderService getOrder "{params,returnObj}" -x 2

# 输出:
# params: [1001]
# returnObj: null

# 说明传入 ID=1001 时,返回了 null

案例三:动态修改日志级别

bash 复制代码
# 查看当前日志级别
$ ogl @org.slf4j.LoggerFactory@getLogger("com.example.demo").getLevel()

# 修改日志级别为 DEBUG
$ ogl @ch.qos.logback.classic.Logger@setLevel(ch.qos.logback.classic.Level@DEBUG)

案例四:查看 JVM 信息

bash 复制代码
# 查看 JVM 信息
$ jvm

# 输出:
# JVM information:
#   JVM version: 17.0.5+9-LTS-191
#   JVM args: -Xms512m -Xmx1024m -XX:+UseG1GC
#   Java home: /usr/lib/jvm/java-17
#   ...

4.5 Arthas 进阶技巧

技巧一:火焰图

bash 复制代码
# 生成 CPU 火焰图
$ profiler start

# 停止并生成火焰图
$ profiler stop --format html > flamegraph.html

技巧二:热更新代码

bash 复制代码
# 编译修改后的类
$ mc /path/to/ModifiedClass.java

# 重新加载类
$ retransform /path/to/ModifiedClass.class

技巧三:保存结果

bash 复制代码
# 将结果保存到文件
$ watch com.example.demo.service.OrderService getOrder "{params,returnObj}" > watch.log

5. 排查流程最佳实践

5.1 常见问题的排查步骤

问题一:CPU 100%

  1. 使用 top -p <pid> 找到最耗 CPU 的线程
  2. 将线程 ID 转换为十六进制:printf "%x\n" <tid>
  3. 使用 jstack <pid> | grep <hex-tid> 找到线程堆栈
  4. 分析堆栈,找出死循环或密集计算

问题二:内存持续增长

  1. 使用 jstat -gc <pid> 观察 GC 情况
  2. 如果 Full GC 频繁且内存持续增长,可能是内存泄漏
  3. 使用 jmap -dump 导出 heap dump
  4. 使用 MAT 分析,找出泄漏对象

问题三:线程阻塞

  1. 使用 jstack -l <pid> 查看线程状态
  2. 搜索 BLOCKED、WAITING 状态的线程
  3. 分析锁竞争或等待条件
  4. 如果有死锁,jstack 会自动报告

问题四:响应超时

  1. 使用 Arthas 的 trace 追踪方法调用链
  2. 找出耗时最长的方法
  3. 检查是 CPU 密集还是 I/O 密集
  4. 如果是外部调用超时,检查网络和下游服务

5.2 排查工具对比

工具 用途 优点 缺点
jstack 线程分析 JDK 自带,零依赖 信息有限
jmap 堆内存分析 JDK 自带 需要导出文件
jstat JVM 统计 实时监控 信息有限
Arthas 全面诊断 功能强大,无需重启 需要学习成本
MAT 内存分析 功能强大,可视化 需要导出文件
VisualVM 综合分析 可视化 功能一般

5.3 排查注意事项

注意一:生产环境谨慎操作

  • jstack、jmap 等命令会暂停 JVM(STW),大内存堆可能影响服务
  • 建议先在测试环境验证
  • 优先使用 Arthas 的非侵入式操作

注意二:保留现场

  • 排查前先保存现场:jstack、jmap 输出
  • 记录时间点、现象、复现步骤
  • 方便后续分析和回溯

注意三:不要只看表面

  • 线程多不一定有问题,可能是正常业务
  • 内存高不一定泄漏,可能是缓存
  • 找到根因再修复,不要「头痛医头」

6. 综合示例:线上问题排查实战

6.1 问题描述

线上服务在高峰期出现响应超时,CPU 使用率正常,但请求堆积。

6.2 排查过程

步骤一:初步诊断

bash 复制代码
# 查看进程状态
$ jps -l
12345 com.example.demo.DemoApplication

# 查看线程数
$ jstack 12345 | grep "Thread-" | wc -l
200

# 查看 CPU 和内存
$ top -p 12345

发现线程数达到 200,偏高。

步骤二:分析线程堆栈

bash 复制代码
$ jstack -l 12345 > jstack.log
$ grep -c "BLOCKED" jstack.log
150

发现 150 个线程处于 BLOCKED 状态!

步骤三:定位阻塞点

bash 复制代码
$ grep -A 10 "BLOCKED" jstack.log | head -50

发现大量线程在等待 com.example.demo.service.OrderService 的锁。

步骤四:分析锁持有者

bash 复制代码
$ grep -B 5 "locked <" jstack.log | grep -A 5 "Thread-"

发现 Thread-1 持有锁且处于 RUNNABLE 状态,查看其堆栈:

复制代码
"Thread-1" #15 prio=5 os_prio=0 tid=0x00007f8a5c01e800 nid=0x7a2c runnable [0x00007f8a5a7e7000]
   java.lang.Thread.State: RUNNABLE
    at com.example.demo.service.OrderService.getOrder(OrderService.java:45)
    - locked <0x00000000d5f8b828> (a com.example.demo.service.OrderService)
    at com.example.demo.service.OrderService.getOrder(OrderService.java:30)
    at com.example.demo.controller.OrderController.getOrder(OrderController.java:20)

步骤五:定位根因

查看 OrderService.java 第 45 行:

java 复制代码
public Order getOrder(Long id) {
    synchronized (this) {  // 问题:锁粒度太大
        Order order = orderRepository.findById(id).orElse(null);
        // 模拟耗时操作
        try {
            Thread.sleep(5000);  // 问题:同步块内调用 sleep!
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return order;
    }
}

根因 :同步块内调用了 Thread.sleep(5000),导致其他线程阻塞 5 秒!

步骤六:修复问题

java 复制代码
public Order getOrder(Long id) {
    // 只在需要同步的地方加锁
    Order order = orderRepository.findById(id).orElse(null);
    if (order != null) {
        // 处理订单(不需要锁)
        processOrder(order);
    }
    return order;
}

6.3 总结

这个案例展示了完整的排查流程:

  1. 发现异常:线程数多、BLOCKED 线程多
  2. 定位阻塞点:jstack 分析
  3. 找到根因:同步块内 sleep
  4. 修复问题:减小锁粒度

小结

  • jstack 是线程分析的核心工具,可以查看线程状态、定位死锁、分析阻塞原因
  • heap dump 是内存分析的核心,通过 MAT 等工具可以找到内存泄漏的根因
  • Arthas 是功能最强大的在线诊断工具,支持方法监控、调用链追踪、热更新等高级功能
  • 排查的核心思路是「看穿表象,找到根因」:CPU 高 → 死循环?内存涨 → 泄漏?线程卡 → 锁竞争?
  • 排查时要注意生产环境的特殊性,优先使用非侵入式工具,保留现场便于分析

P1 阶段小结

从第 013 篇到第 032 篇,我们完成了 Java 语言 + JVM + 并发的全部 20 篇文章。这个阶段我们学习了:

  • Java 基础:内存模型、基本类型、String、集合、泛型、异常、IO、反射
  • JVM 原理:运行时数据区、对象创建、GC、类加载
  • 并发编程:volatile、synchronized、锁、线程池、j.u.c、并发设计
  • 排查工具:jstack、heap dump、Arthas

这些知识是 Java 开发者的核心能力,也是后续 Spring Boot、分布式、微服务的基础。

P2 阶段预告:从第 033 篇开始,我们将进入 P2 工程化与协作阶段,包括 Maven/Gradle、Git、单元测试、CI/CD 等主题。这些是每个 Java 工程师必备的工程能力。

相关推荐
pq2171 小时前
Spring FactoryBean源码解析
java·spring boot·spring
其实防守也摸鱼1 小时前
无线网络安全--实验 规避WLAN验证之发现隐藏的SSID
java·开发语言·网络·安全·web安全·智能路由器·无线网络安全
l1t1 小时前
astral-sh发布的musl和gnu版本standalone python 性能比较
开发语言·python
书源丶2 小时前
四十三、网络编程(下)——TCP 编程与 HTTP 入门
java·网络·tcp/ip·http
木井巳2 小时前
【递归算法】单词搜索
java·算法·leetcode·决策树·深度优先
阿豪只会阿巴2 小时前
【没事学点啥】TurboBlog轻量级个人博客项目——Turbo Blog 项目学习与上线指南
开发语言·python·学习·状态模式
铁皮饭盒2 小时前
成为AI全栈 - 第4课:Drizzle ORM SQLite Elysia 数据库实战
前端·后端
幸运的大号暖贴2 小时前
解决Vibe Coding时Idea经常不自动git add问题
java·人工智能·git·intellij-idea·claudecode·opencode
m0_716255002 小时前
第一部分 数据开发 面试全题 模拟口述版(自问自答)
java·数据库·面试