perf
是Linux性能分析工具的集合,它提供了丰富的命令来收集和分析程序运行时的性能数据。perf
能够报告CPU使用率、缓存命中率、分支预测成功率等多种硬件级别的事件,同时也支持软件级别的事件,如页面错误、任务切换等。perf
是理解程序性能瓶颈、进行性能优化的重要工具。
perf
的主要作用perf
的基本用法安装:在大多数Linux发行版中,perf
工具包含在linux-tools
包中。可以通过包管理器安装,例如,在Ubuntu上使用sudo apt-get install linux-tools-common linux-tools-generic
。
列出可用事件:使用perf list
命令可以查看所有可监控的事件。
性能统计:perf stat
命令用于收集和报告程序的性能统计信息。例如,perf stat ./your_program
会运行your_program
并报告其性能统计。
记录性能事件:perf record
命令用于记录特定性能事件。例如,perf record -e cycles ./your_program
会记录程序运行时的CPU周期事件。
生成报告:perf report
命令用于分析perf record
收集的数据,并生成报告。这个报告可以帮助开发者定位性能瓶颈。
注解源代码:perf annotate
命令用于将性能事件映射到源代码上,帮助开发者理解哪些代码行是热点。
查看调用栈:使用perf record
时加上-g
选项可以记录调用栈信息,然后使用perf report
查看,这对于理解函数间的调用关系非常有用。
收集性能统计:
perf stat -e cache-misses,cache-references,instructions,cycles ./your_program
这个命令会运行your_program
,并报告缓存未命中次数、缓存引用次数、指令数和CPU周期数。
记录和报告性能数据:
perf record -g ./your_program
perf report
这会记录your_program
的性能数据,并生成一个性能报告,报告中包含了热点函数和调用栈信息。
通过使用perf
工具,开发者可以深入了解程序的运行时性能,从而进行有效的性能优化。
分析Java项目的性能时,perf
可以帮助你理解底层的系统和硬件层面的性能问题,但由于Java运行在虚拟机(JVM)之上,直接使用perf
可能不足以提供足够的信息来定位到具体的Java方法。不过,通过结合使用perf
和其他工具,可以有效地分析Java应用的性能。以下是一些步骤和技巧:
为了让perf
能够识别Java方法,需要确保JVM启动时带有适当的参数来导出本地符号。对于HotSpot JVM,可以使用以下参数:
-XX:+PreserveFramePointer -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints
这些参数的作用是:
-XX:+PreserveFramePointer
:保留帧指针,以便perf
可以构建调用栈。-XX:+UnlockDiagnosticVMOptions
:解锁诊断选项。-XX:+DebugNonSafepoints
:确保即使在非安全点也能生成足够的调试信息。perf record
收集性能数据运行你的Java应用,并使用perf record
来收集性能数据。例如:
perf record -F 99 -p $(pgrep -n java) -g -- sleep 30
这个命令会:
-F 99
:以每秒99次的频率采样。-p $(pgrep -n java)
:指定要监控的进程ID,这里使用pgrep
来找到Java进程的PID。-g
:记录调用栈信息,以便分析。-- sleep 30
:收集30秒的性能数据。perf report
分析性能数据收集完数据后,使用perf report
来查看性能报告。这将显示CPU使用最多的函数和调用栈。
perf-map-agent
和FlameGraph
工具由于perf
默认可能无法解析Java方法名称,你可以使用perf-map-agent
来生成Java方法的映射文件,然后perf
就能识别Java方法了。perf-map-agent
是一个开源工具,可以在GitHub上找到。
perf
默认情况下可能无法解析Java方法名称,因为Java方法在运行时是由Java虚拟机(JVM)动态编译的,而不是直接作为二进制代码存在。为了让perf
能够识别Java方法,可以使用perf-map-agent
来生成一个包含Java方法名称和相应内存地址的映射文件。这个映射文件随后可以被perf
使用,以便在性能分析报告中显示具体的Java方法名称。
perf-map-agent
的步骤perf-map-agent
:gcc
和make
),因为安装perf-map-agent
可能需要编译一些源代码。perf-map-agent
的GitHub仓库:git clone https://github.com/jvm-profiling-tools/perf-map-agent.git
perf-map-agent
目录,并使用cmake
和make
编译项目:cd perf-map-agent
cmake .
make
perf-map-agent
生成映射文件。这通常涉及到运行一个特定的脚本或命令,该命令会附加到运行中的JVM进程,并生成映射文件/tmp/perf-.map
,其中
是JVM进程的ID。create-java-perf-map.sh
脚本(这个脚本随perf-map-agent
提供):./create-java-perf-map.sh <pid>
这里
是你的Java应用的进程ID。perf
进行性能分析:perf record
和perf report
进行性能分析时,perf
将能够利用生成的映射文件来解析Java方法名称。perf record -F 99 -p <pid> -g -- sleep 30
perf report
perf report
查看性能报告时,你应该能看到具体的Java方法名称,而不仅仅是内存地址。这使得分析Java应用的性能问题变得更加直观和容易。perf
版本和Linux内核版本兼容,以便正确地解析映射文件。perf-map-agent
时,可能需要根据你的系统环境调整编译命令或脚本。通过使用perf-map-agent
,perf
工具能够提供更加详细和有用的性能分析数据,帮助开发者优化Java应用的性能。
生成映射文件后,你还可以使用Brendan Gregg的FlameGraph
工具来生成火焰图,这是一种直观展示性能瓶颈的图表。
FlameGraph是一个强大的工具,用于可视化和分析软件性能,特别是CPU使用情况。对于Java项目,结合perf
、perf-map-agent
和FlameGraph,可以有效地识别性能热点和瓶颈。以下是如何使用这些工具来分析和优化Java项目的步骤:
确保Java应用以允许性能分析的模式运行。这通常意味着需要使用特定的JVM参数,如-XX:+PreserveFramePointer
,以便perf
能够获取到更准确的调用栈信息。
使用perf
收集性能数据。找到Java应用的进程ID(PID),然后运行:
perf record -F 99 -p [PID] -g -- sleep [duration]
其中[PID]
是Java进程的ID,[duration]
是收集数据的时间长度(秒)。
安装perf-map-agent
。按照之前的指导安装并编译perf-map-agent
。
生成映射文件。在Java应用运行的同时,使用perf-map-agent
为perf
生成映射文件:
cd perf-map-agent/out
java -cp attach-main.jar:$JAVA_HOME/lib/tools.jar net.virtualvoid.perf.AttachOnce [PID]
这将为perf
生成一个映射文件,使其能够解析Java方法名称。
使用perf
生成堆栈折叠文件:
perf script > out.perf
使用FlameGraph工具生成火焰图:
cd FlameGraph
./stackcollapse-perf.pl ../out.perf > out.folded
./flamegraph.pl out.folded > flamegraph.svg
这将生成一个名为flamegraph.svg
的火焰图文件,你可以使用任何支持SVG格式的浏览器或图像查看器打开它。
图片示例
jstack
对于Java应用,jstack
是一个非常有用的工具,它可以生成Java线程的堆栈跟踪。通过将perf
的输出与jstack
的输出相结合,可以更准确地定位性能问题。
jstack
是一个用于生成 Java 线程堆栈跟踪的工具,它可以帮助开发者分析和诊断 Java 应用程序的性能问题,特别是线程相关的问题。以下是如何使用 jstack
分析 Java 项目的步骤:
首先,你需要找到正在运行的 Java 应用程序的进程 ID(PID)。你可以使用 jps
或 ps
命令来找到 PID。
jps -l
或者:
ps -ef | grep java
使用 jstack
命令生成指定 Java 进程的线程堆栈跟踪。假设 PID 是 12345
,你可以运行:
jstack -l 12345 > thread_dump.txt
这将生成一个包含所有线程堆栈跟踪的文件 thread_dump.txt
。
打开 thread_dump.txt
文件,分析其中的线程堆栈信息。以下是一些常见的分析方法:
查找死锁:jstack
会自动检测死锁并在输出中报告。如果存在死锁,你会在输出中看到类似 “Found one Java-level deadlock” 的信息。
分析线程状态:每个线程的堆栈跟踪信息会包含线程的状态(如 RUNNABLE
、BLOCKED
、WAITING
等)。通过分析线程状态,可以了解线程是否在等待资源、被阻塞或正在运行。
查找热点代码:如果某些线程的堆栈跟踪信息显示它们在相同的方法中花费了大量时间,这些方法可能是性能瓶颈。你可以重点分析这些方法,寻找优化的机会。
分析锁争用:如果多个线程在等待相同的锁,可能会导致性能问题。通过分析堆栈跟踪信息中的锁信息,可以识别锁争用的情况。
以下是一个简单的 jstack
输出示例:
"main" #1 prio=5 os_prio=0 tid=0x00007f8c5400a000 nid=0x1b03 runnable [0x00007f8c58bfe000]
java.lang.Thread.State: RUNNABLE
at com.example.MyClass.myMethod(MyClass.java:123)
at com.example.MyClass.run(MyClass.java:456)
at java.lang.Thread.run(Thread.java:748)
"Thread-1" #2 prio=5 os_prio=0 tid=0x00007f8c5400b000 nid=0x1b04 waiting on condition [0x00007f8c58cff000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000d5c6c6c8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:231)
at com.example.MyClass.run(MyClass.java:789)
at java.lang.Thread.run(Thread.java:748)
在这个示例中:
main
)处于 RUNNABLE
状态,正在执行 com.example.MyClass.myMethod
方法。Thread-1
线程处于 WAITING
状态,正在等待一个 ReentrantLock
。优化热点代码:对于处于 RUNNABLE
状态的线程,分析其堆栈跟踪信息,找出占用大量时间的方法,并进行优化。
减少锁争用:对于处于 BLOCKED
或 WAITING
状态的线程,分析其等待的锁,尝试减少锁的持有时间或使用更细粒度的锁。
避免死锁:如果检测到死锁,分析相关线程的堆栈跟踪信息,找出死锁的原因,并修改代码以避免死锁。
通过使用 jstack
生成和分析线程堆栈跟踪信息,可以有效地诊断和优化 Java 应用程序的性能问题。
虽然perf
主要用于分析底层系统和硬件性能,但通过上述方法,它也可以结合使用perf-map-agent
、FlameGraph
、jstack
工具,成为分析Java应用性能的有力工具。