入门指南:深入理解 Binder 驱动的调试方法
系统学习 Framework 教程合集:yuandaimaahao.github.io/AndroidFram…
什么是 Debugfs
DebugFS,是一种用于内核调试的虚拟文件系统,内核中提供了相应的函数,将一些日志信息写入这个虚拟文件系统,内核的使用者通过查看这个虚拟文件系统对应的文件,即可了解内核中对用模块的运行状况。
Debugfs 中的 Binder
debugfs 文件系统默认挂载在节点 /sys/kernel/debug,binder 驱动初始化的过程会在该节点下先创建 /binder 目录,然后在该目录下创建下面文件和目录:
- proc
- stats
- state
- transactions
- transaction_log
- failed_transaction_log
在 binder 驱动的 binder_init 函数中:
// drivers/android/binder.c
static int __init binder_init(void)
{
//.....
// 在 /sys/kernel/debug 目录下创建 binder 文件夹
binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL);
if (binder_debugfs_dir_entry_root)
binder_debugfs_dir_entry_proc = debugfs_create_dir("proc",
binder_debugfs_dir_entry_root);
if (binder_debugfs_dir_entry_root) {
// 在 /sys/kernel/debug 目录下创建 binder 文件夹
debugfs_create_file("state",
0444,
binder_debugfs_dir_entry_root,
NULL,
&binder_state_fops);
debugfs_create_file("stats",
0444,
binder_debugfs_dir_entry_root,
NULL,
&binder_stats_fops);
debugfs_create_file("transactions",
0444,
binder_debugfs_dir_entry_root,
NULL,
&binder_transactions_fops);
debugfs_create_file("transaction_log",
0444,
binder_debugfs_dir_entry_root,
&binder_transaction_log,
&binder_transaction_log_fops);
debugfs_create_file("failed_transaction_log",
0444,
binder_debugfs_dir_entry_root,
&binder_transaction_log_failed,
&binder_transaction_log_fops);
}
// ......
}
binder 相关的调试文件就是通过上述的代码完成创建的。
在 binder 驱动源码中有很多 binder_debug 函数调用:
binder_debug(BINDER_DEBUG_OPEN_CLOSE,
"%s: %d threads %d, nodes %d (ref %d), refs %d, active transactions %d\n",
__func__, proc->pid, threads, nodes, incoming_refs,
outgoing_refs, active_transactions);
binder_debug(BINDER_DEBUG_SPINLOCKS,
"%s: line=%d\n", __func__, line);
binder_debug(BINDER_DEBUG_INTERNAL_REFS,
"refless node %d deleted\n",
node->debug_id);
//......
binder_debug 函数会把日志信息写入到我们创建好的 debugfs 中的文件中。binder_debug 的第一个参数叫 debug_mask,它决定了我们日志的类型与日志所在的文件。
内核模块参数
有的时候,在执行一些内核模块时,需要我们传入一些参数,内核提供了 module_param
宏来实现这个功能:
//假设模块名为 xxx
//定义一个内核模块参数
static unsigned int int_var = 0;
module_param(int_var, uint, S_IRUGO);
上面的代码定义了一个模块参数 int_var
,我们可以在执行模块时传入参数值:
//执行内核模块时,传入参数
insmod xxx.ko int_var=x
除了在执行时传入参数值,我们还可以通过写文件来传参数:每一个模块参数会对应一个文件 /sys/module/模块名/parameters/参数名
,上面的示例中的模块参数对应的文件就是 /sys/module/xxx/parameters/int_var
,那么我们通过写这个文件就能把参数传入模块中,向文件写数据,我们可以通过 C 语言实现,也可以使用 shell 命令实现,这里我们给一个 shell 脚本的例子:
echo 8 > /sys/module/xxx/parameters/int_var
执行完这个命令后,在模块中的 int_var 参数的值就变成 8 了。
回过头来,module_param 的具体实现如下:
#define module_param(name, type, perm) \
module_param_named(name, name, type, perm)
可以看出 module_param 就是 module_param_named 的马甲:
// module_param_named 的具体实现就不再深入了,了解功能即可,这个不是本文重点
#define module_param_named(name, value, type, perm) \
param_check_##type(name, &(value)); \
module_param_cb(name, ¶m_ops_##type, &value, perm); \
__MODULE_PARM_TYPE(name, #type)
module_param_named 相比 module_param ,可以给模块参数一个初始值 value。
Binder 驱动中的模块参数
在 Binder 驱动中都是使用的 module_param_named
来定义模块参数。接下来我们来看一个 Binder 驱动中的模块参数 debug_mask,其定义如下:
static uint32_t binder_debug_mask = BINDER_DEBUG_USER_ERROR |
BINDER_DEBUG_FAILED_TRANSACTION | BINDER_DEBUG_DEAD_TRANSACTION;
module_param_named(debug_mask, binder_debug_mask, uint, 0644);
上面的代码执行后,就会生成对应的文件 /sys/module/binder/parameters/debug_mask
,当我们修改这个文件的值时,就会修改 Binder 驱动中 debug_mask 的值。
debug_mask 是一个无符号整型,有一个初始值 binder_debug_mask,这个模块参数扮演了 Log 开关的角色,通过设置 debug_mask 的值可以打印我们需要的 Log 信息。debug_mask 通过二进制或操作
设置 uint32_t 类型中的每一个位的值来决定其功能。
具体来说,debug_mask 有以下一些可选值:
类型 | 二进制值 | 解释 |
---|---|---|
BINDER_DEBUG_USER_ERROR | 1 | 用户使用错误 |
BINDER_DEBUG_FAILED_TRANSACTION | 10 | transaction失败 |
BINDER_DEBUG_DEAD_TRANSACTION | 100 | transaction死亡 |
BINDER_DEBUG_OPEN_CLOSE | 1000 | binder的open/close/mmap信息 |
BINDER_DEBUG_DEAD_BINDER | 10000 | binder/node死亡信息 |
BINDER_DEBUG_DEATH_NOTIFICATION | 100000 | binder死亡通知信息 |
BINDER_DEBUG_READ_WRITE | 1000000 | binder的read/write信息 |
BINDER_DEBUG_USER_REFS | 10000000 | binder引用计数 |
BINDER_DEBUG_THREADS | 100000000 | binder_thread信息 |
BINDER_DEBUG_TRANSACTION | 1000000000 | transaction信息 |
BINDER_DEBUG_TRANSACTION_COMPLETE | 10000000000 | transaction完成信息 |
BINDER_DEBUG_FREE_BUFFER | 100000000000 | 可用buffer信息 |
undefinedBINDER_DEBUG_INTERNAL_REFS | 1000000000000 | binder内部引用计数 |
BINDER_DEBUG_BUFFER_ALLOC | 10000000000000 | 同步内存分配信息 |
BINDER_DEBUG_PRIORITY_CAP | 100000000000000 | 调整binder线程的nice值 |
BINDER_DEBUG_BUFFER_ALLOC_ASYNC | 1000000000000000 | 异步内存分配信息 |
比如我们需要打印 transaction 失败
与 transaction死亡
信息,那么我们就可以把 binder_debug_mask 的值设置为 10 | 100 = 110
即十进制的 6 即可:
echo 6 > /sys/module/binder/parameters/debug_mask
在内核源码中我们使用 binder_debug 打印 Log 信息到 debugfs 文件系统中:
#define binder_debug(mask, x...) \
do { \
if (binder_debug_mask & mask) \
pr_info(x); \
} while (0)
我们也可以通过 binder_debug 的第一个参数来添加 binder_debug_mask
的值。
节点分析
在进行 Binder debug 或分析问题时,通常需要看一下当前的 Binder 状态信息。Kernel 通过 SYS 系统提供了一些文件节点供我们读取,它们位于 /sys/kernel/debug/binder/,分别为:
- Stats:Binder传输的统计信息。
- State:当前Binder所有进程的状态。
- Transactions:当前Binder所有进程的传输状态。
- transaction_log:最近的Binder传输。
- failed_transaction_log:最近失败的Binder传输。
Stats
Stats 包含的 Binder 的统计信息,包括传输命令的统计,内部对象的统计等。开始输出的是全部 Binder 的统计信息,之后按 Binder 进程逐个输出统计信息。
通过
cat /sys/kernel/debug/binder/stats
命令即可查看 Stats 信息:
binder stats:
# Binder BC 命令计数
BC_TRANSACTION: 21561
BC_REPLY: 19102
BC_FREE_BUFFER: 97947
BC_INCREFS: 12458
BC_ACQUIRE: 12650
BC_RELEASE: 10812
BC_DECREFS: 10156
BC_INCREFS_DONE: 10691
BC_ACQUIRE_DONE: 10692
BC_REGISTER_LOOPER: 145
BC_ENTER_LOOPER: 132
BC_REQUEST_DEATH_NOTIFICATION: 861
BC_CLEAR_DEATH_NOTIFICATION: 374
BC_DEAD_BINDER_DONE: 14
BC_TRANSACTION_SG: 33879
BC_REPLY_SG: 23474
# Binder BR 返回命令计数
BR_TRANSACTION: 55440
BR_REPLY: 42575
BR_TRANSACTION_COMPLETE: 98014
BR_INCREFS: 10704
BR_ACQUIRE: 10705
BR_RELEASE: 9793
BR_DECREFS: 9781
BR_SPAWN_LOOPER: 149
BR_DEAD_BINDER: 16
BR_CLEAR_DEATH_NOTIFICATION_DONE: 374
# Binder 内部对象计数,active 表示有效的对象数量,total 表示开机到现在总共创建的对象数量
proc: active 99 total 1336
thread: active 456 total 1992
node: active 912 total 10843
ref: active 1720 total 12622
death: active 484 total 861
transaction: active 1 total 98016
transaction_complete: active 1 total 98016
# 进程 PID
proc 3257
context binder
threads: 4 # Binder 线程数
requested threads: 0+1/15 # 请求线程数 + 已启动线程数/最大线程数
ready threads 2 # 已经准备好的线程数
free async space 520192 # 异步传输可用空间,单位字节
nodes: 5 # binder_node 结构体数量
refs: 24 s 24 w 24 # 引用计数, s 强 w 弱
buffers: 0 # allocated_buffers (已分配的buffer个数)
pages: 0:2:252 # page 相关信息
pages high watermark: 2
pending transactions: 0 # proc 的 todo 队列事务个数
# 当前进程 binder BC 命令计数
BC_TRANSACTION: 32
BC_FREE_BUFFER: 34
BC_INCREFS: 25
BC_ACQUIRE: 25
BC_RELEASE: 1
BC_DECREFS: 1
BC_INCREFS_DONE: 5
BC_ACQUIRE_DONE: 5
BC_REGISTER_LOOPER: 1
BC_ENTER_LOOPER: 1
# 当前进程 Binder BR 返回命令计数
BR_TRANSACTION: 3
BR_REPLY: 31
BR_TRANSACTION_COMPLETE: 32
BR_INCREFS: 5
BR_ACQUIRE: 5
BR_SPAWN_LOOPER: 1
# ...... 其他进程信息
命令计数的规律如下: BC_TRANSACTION + BC_REPLY = BR_TRANSACTION_COMPLETE + BR_DEAD_REPLY + BR_FAILED_REPLY
为什么是会是这样呢,因为每次 BC_TRANSACTION 或着 BC_REPLY,都是有相应的 BR_TRANSACTION_COMPLETE,在传输不出异常的情况下这个次数是相等,有时候可能 transaction 失败, 所以还需要加上 BR_DEAD_REPLY 和 BR_FAILED_REPLY 的情况.
- 当 binder 内存紧张时,可查看 free async space和buffers:字段:
- 当系统空闲时,一般来说 ready_threads = requested_threads_started + BC_ENTER_LOOPER; 当系统繁忙时 ready_threads 可能为0。例如 system_server 进程的 ready_threads 线程个数越少,系统可能处于越繁忙的状态;
- 绝大多数的进程 max_threads = 15,而 surfaceflinger 最大线程个数为 4,servicemanager 最大线程个数为 0(只有主线程);
举个实际的例子:想查看当前系统所有进程的异步可用内存情况,可执行:
adb shell cat /d/binder/stats | egrep "proc|free async space"
State
State 中主要记录了当前所有进程的 Binder 状态。
通过
cat /sys/kernel/debug/binder/state
命令即可查看 State 信息:
binder state:
# 死亡节点的信息
# 记录的时死亡的 Binder(dead node)。因为这些Binder的进程已经死掉,它们当前不属于任何进程,所以将它们记录在一个全局的队列中(binder_proc 的 binder_dead_nodes 中)
dead nodes:
node 34015: u00007231e521b760 c00007231e518cdd0 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
node 33574: u00007231e521b6e0 c00007231e50c2170 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
node 14081: u00007231e521b3c0 c00007231e50c1040 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
node 14079: u00007231e521b200 c00007231e5183610 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
node 13553: u00007231e52117a0 c00007231e518cdd0 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
node 13164: u00007231e5211240 c00007231e50c2170 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
node 29997: u00007231e521b9a0 c00007231e518cdd0 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
node 29915: u00007231e521b960 c00007231e50c2170 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
node 31122: u0000723154d204c0 c00007231e50c1540 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
node 35433: u00007231e521b9a0 c00007231e518cdd0 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
node 35303: u00007231e521b960 c00007231e50c2170 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
node 660: u00007bd4d168a7a0 c00007bd4d16066f8 pri 0:120 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
# ......
开头信息记录的是死亡的 Binder(dead node)。因为这些 Binder 的进程已经死掉,它们当前不属于任何进程,所以将它们记录在一个全局的队列中(binder_proc 的 binder_dead_nodes 中)
我们来看下第一个 node:
# debug_id ptr cookie
node 34015: u00007231e521b760 c00007231e518cdd0 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
- 34015:对应 binder_node 中的 debug_id
- u00007231e521b760:对应 binder_node 中的 ptr
- c00007231e518cdd0:对应 binder_node 中的 cookie
- hs:对应 binder_node 中的 has_strong_ref,表示是否存在强引用
- hw:对应 binder_node 中的 has_weak_ref,表示是否存在弱引用
- ls:对应 binder_node 中的 local_strong_refs,表示是否存在驱动内部的 binder 强引用计数
- lw:对应 binder_node 中的 local_weak_refs,表示驱动内部的 binder 弱引用计数
- is: 对应 binder_node 中的 internal_strong_refs,表示 binder node 的外部强引用计数
- iw:binder_ref 的引用数
- proc:对应的进程 pid
我们接着往下看,就是进程相关的信息了:
# 进程 pid
proc 3257
# 线程状态
context binder
thread 3257: l 00 need_return 0 tr 0
thread 3270: l 00 need_return 0 tr 0
thread 3273: l 12 need_return 0 tr 0
thread 3274: l 11 need_return 0 tr 0
# binder node 信息
node 56400: u0000723154c2d5a0 c00007231e50c0fa0 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
node 56414: u0000723154c2d640 c00007231e50c1130 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
node 56402: u0000723154c2d6e0 c00007231e50c1090 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
node 56346: u00007231e5280220 c00007231e50c0aa0 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
node 56376: u00007231e52802a0 c00007231e50c0af0 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 1772
# binder ref 信息
ref 56341: desc 0 node 1 s 1 w 1 d 0000000000000000
ref 56344: desc 1 node 2125 s 1 w 1 d 0000000000000000
ref 56349: desc 2 node 1663 s 1 w 1 d 0000000000000000
ref 56350: desc 3 node 2727 s 1 w 1 d 0000000000000000
ref 56351: desc 4 node 3729 s 1 w 1 d 0000000000000000
ref 56352: desc 5 node 2078 s 1 w 1 d 0000000000000000
ref 56353: desc 6 node 2844 s 1 w 1 d 0000000000000000
ref 56354: desc 7 node 2969 s 1 w 1 d 0000000000000000
ref 56355: desc 8 node 2116 s 1 w 1 d 0000000000000000
ref 56356: desc 9 node 2788 s 1 w 1 d 0000000000000000
ref 56357: desc 10 node 2848 s 1 w 1 d 0000000000000000
ref 56358: desc 11 node 2996 s 1 w 1 d 0000000000000000
ref 56359: desc 12 node 1712 s 1 w 1 d 0000000000000000
ref 56360: desc 13 node 3304 s 1 w 1 d 0000000000000000
ref 56361: desc 14 node 2731 s 1 w 1 d 0000000000000000
ref 56362: desc 15 node 2965 s 1 w 1 d 0000000000000000
ref 56363: desc 16 node 3669 s 1 w 1 d 0000000000000000
ref 56364: desc 17 node 2774 s 1 w 1 d 0000000000000000
ref 56365: desc 18 node 1790 s 1 w 1 d 0000000000000000
ref 56366: desc 19 node 3270 s 1 w 1 d 0000000000000000
ref 56367: desc 20 node 3136 s 1 w 1 d 0000000000000000
ref 56419: desc 21 node 5111 s 1 w 1 d 0000000000000000
ref 56421: desc 22 node 56420 s 1 w 1 d 0000000000000000
ref 56426: desc 23 node 378 s 1 w 1 d 0000000000000000
# binder buffer 信息
buffer 32426: 0000000000000000 size 4:0:0 delivered
buffer 20810: 0000000000000000 size 8:0:0 delivered
Transactions
Transactions 中记录了当前系统中所有 Binder 进程的传输状态:
binder transactions:
context binder
#buffer id 数据地址 数据长度与偏移量 active/delivered
buffer 29494: 0000000000000000 size 8:0:0 delivered
buffer 35759: 0000000000000000 size 52:8:0 delivered
transaction_log 和 failed_transaction_log
transaction_log 中记录了最近的 Binder 传输,failed_transaction_log 中记录了最近失败的 Binder 传输。
121480: call from 1912:2231 to 1408:0 context binder node 1 handle 0 size 100:0 ret 0/0 l=0
121481: reply from 1408:1408 to 1912:2231 context binder node 0 handle 0 size 4:0 ret 0/0 l=0
- 121438: debug_id
- reply from 和 call from : call_type
- from 1524:1524 to 1772:1848 :from_proc:from_thread to_proc:to_thread
- node 1:to_node
- handle:target_handle
- size 100:0:data_size:offsets_size
参考资料
- android 系统核心机制binder(14)binder调试总结
- Binder子系统之调试分析(一)
- Binder子系统之调试分析(二)
- Binder驱动之debug信息与数据结构
关于
我叫阿豪,2015 年本科毕业于国防科学技术大学指挥信息系统专业,毕业后从事信息化装备的研发工作,主要研究方向是 Android Framework 与 Linux Kernel。
如果你对 Android Framework 感兴趣或者正在学习 Android Framework,可以关注我的微信公众号和抖音,我会持续分享我的学习经验,帮助正在学习的你少走一些弯路。学习过程中如果你有疑问或者你的经验想要分享给大家可以添加我的微信,我拉你进技术交流群。
推荐阅读
-
入门指南:深入理解 Binder 驱动的调试方法
-
深入理解SPI通信协议,并掌握其驱动程序的编写方法实例
-
STM32H7入门指南:第91节——深入理解STM32H7的FDCAN总线及其HAL库接口
-
STM32H7入门指南:第91节——深入理解STM32H7的FDCAN总线及其HAL库接口
-
Java 类加载器的作用 - 简介:类加载器是 Java™ 中一个非常重要的概念。类加载器负责将 Java 类的字节码加载到 Java 虚拟机中。本文首先详细介绍了 Java 类加载器的基本概念,包括代理模型、加载类的具体过程和线程上下文类加载器等。然后介绍了如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi™ 中的应用。 类加载器是 Java 语言的一项创新,也是 Java 语言广受欢迎的重要原因之一。它允许将 Java 类动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 开始出现,最初是为了满足 Java Applets 的需求而开发的,Java Applets 需要从远程位置下载 Java 类文件并在浏览器中执行。现在,类加载器已广泛应用于网络容器和 OSGi。一般来说,Java 应用程序的开发人员不需要直接与类加载器交互;Java 虚拟机的默认行为足以应对大多数情况。但是,如果遇到需要与类加载器交互的情况,而您又不太了解类加载器的机制,就很容易花费大量时间调试异常,如 ClassNotFoundException 和 NoClassDefFoundError。本文将详细介绍 Java 的类加载器,帮助读者深入理解 Java 语言中的这一重要概念。下面先介绍一些基本概念。 类加载器的基本概念 顾名思义,类加载器用于将 Java 类加载到 Java 虚拟机中。一般来说,Java 虚拟机以如下方式使用 Java 类:Java 源程序(.java 文件)经 Java 编译器编译后转换为 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码并将其转换为 java.lang 实例。每个实例都用来表示一个 Java 类。通过该实例的 newInstance 方法创建该类的对象。实际情况可能更加复杂,例如,Java 字节代码可能是由工具动态生成或通过网络下载的。 基本上,所有类加载器都是 java.lang.ClassLoader 类的实例。下面将详细介绍这个 Java 类。 java.lang.ClassLoader 类简介 java.lang.ClassLoader 类的基本职责是根据给定类的名称为其查找或生成相应的字节码,然后根据这些字节码定义一个 Java 类,即 java.lang.Class 类的实例。除此之外,ClassLoader 还负责加载 Java 应用程序所需的资源,如图像文件和配置文件。不过,本文只讨论它加载类的功能。为了履行加载类的职责,ClassLoader 提供了许多方法,其中比较重要的方法如表 1 所示。下文将详细介绍这些方法。 表 1.与加载类相关的 ClassLoader 方法
-
玩转Kotlin性能测试:JMH入门指南一 - 测试基础" "深入理解JMH在Kotlin中的应用:基准测试实战解析" "轻松实践Kotlin基准测试:JMH工具详解与实例总结
-
深入理解如何用哈希表和优先队列实现求前K个元素的解题方法 - Java实战刷题指南
-
深度学习中的不确定性量化:2020年实用技术与应用大解析 - 61页精华解读" 这份报告深入剖析了近年来深度学习领域中不确定性量化(UQ)技术的最新发展,包括其在强化学习(RL)中的运用实例。探讨了贝叶斯近似和集成学习等主流UQ方法在各个具体场景中的广泛应用,比如自动驾驶、目标识别、图像修复、医疗影像分析(如分类和分割)、文本理解(如文本分类和风险评估)、以及生物信息学等多个领域。 报告进一步梳理了UQ方法在深度学习领域的关键应用案例,并针对当前面临的挑战及未来研究方向进行了概览和展望,为这一领域的研究人员和实践者提供了有价值的参考指南。
-
深入理解运维标准化和流程化的建设方法指南
-
实用的命令行工具指南:深入理解jps、jstack、jmap、jhat、jstat与hprof JVM 性能优化与监控方法" - jps: Java虚拟机进程状态工具 - 全面解读 - jstack: 一步步掌握堆栈跟踪工具 - 实战解析 - jmap & jhat: 内存映射与Java堆分析利器 - 详解篇 - jstat: JVM性能统计与监测工具 - 使用详解 - hprof: 堆/CPU性能剖析工具 - 深入学习指南