linux perf

perf是Linux性能分析工具的集合,它提供了丰富的命令来收集和分析程序运行时的性能数据。perf能够报告CPU使用率、缓存命中率、分支预测成功率等多种硬件级别的事件,同时也支持软件级别的事件,如页面错误、任务切换等。perf是理解程序性能瓶颈、进行性能优化的重要工具。

perf的主要作用

  1. 性能分析:通过监控硬件和软件事件,帮助开发者理解程序的性能瓶颈。
  2. 热点函数定位:找出程序中CPU使用时间最多的函数。
  3. 系统监控:监控系统级别的性能指标,如CPU使用率、上下文切换次数等。
  4. 调用栈分析:提供函数调用栈信息,帮助开发者理解函数调用关系。

perf的基本用法

  1. 安装:在大多数Linux发行版中,perf工具包含在linux-tools包中。可以通过包管理器安装,例如,在Ubuntu上使用sudo apt-get install linux-tools-common linux-tools-generic

  2. 列出可用事件:使用perf list命令可以查看所有可监控的事件。

  3. 性能统计perf stat命令用于收集和报告程序的性能统计信息。例如,perf stat ./your_program会运行your_program并报告其性能统计。

  4. 记录性能事件perf record命令用于记录特定性能事件。例如,perf record -e cycles ./your_program会记录程序运行时的CPU周期事件。

  5. 生成报告perf report命令用于分析perf record收集的数据,并生成报告。这个报告可以帮助开发者定位性能瓶颈。

  6. 注解源代码perf annotate命令用于将性能事件映射到源代码上,帮助开发者理解哪些代码行是热点。

  7. 查看调用栈:使用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应用的性能。以下是一些步骤和技巧:

1. 开启JVM的本地符号

为了让perf能够识别Java方法,需要确保JVM启动时带有适当的参数来导出本地符号。对于HotSpot JVM,可以使用以下参数:

-XX:+PreserveFramePointer -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints

这些参数的作用是:

  • -XX:+PreserveFramePointer:保留帧指针,以便perf可以构建调用栈。
  • -XX:+UnlockDiagnosticVMOptions:解锁诊断选项。
  • -XX:+DebugNonSafepoints:确保即使在非安全点也能生成足够的调试信息。
2. 使用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秒的性能数据。
3. 使用perf report分析性能数据

收集完数据后,使用perf report来查看性能报告。这将显示CPU使用最多的函数和调用栈。

4. 使用perf-map-agentFlameGraph工具

由于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的步骤
    1. 安装perf-map-agent
    • 首先,确保你有一个编译环境(如gccmake),因为安装perf-map-agent可能需要编译一些源代码。
    • 克隆perf-map-agent的GitHub仓库:
      git clone https://github.com/jvm-profiling-tools/perf-map-agent.git
      
    • 进入perf-map-agent目录,并使用cmakemake编译项目:
      cd perf-map-agent
      cmake .
      make
      
    1. 生成映射文件
    • 在运行你的Java应用的同时,使用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。
    1. 使用perf进行性能分析
    • 现在,当你使用perf recordperf report进行性能分析时,perf将能够利用生成的映射文件来解析Java方法名称。
    • 进行性能分析:
      perf record -F 99 -p <pid> -g -- sleep 30
      perf report
      
    1. 查看性能报告
    • 使用perf report查看性能报告时,你应该能看到具体的Java方法名称,而不仅仅是内存地址。这使得分析Java应用的性能问题变得更加直观和容易。
注意事项
  • 确保你的perf版本和Linux内核版本兼容,以便正确地解析映射文件。
  • 使用perf-map-agent时,可能需要根据你的系统环境调整编译命令或脚本。
  • 生成的映射文件仅在Java应用运行期间有效。如果Java应用重启,你需要重新生成映射文件。

通过使用perf-map-agentperf工具能够提供更加详细和有用的性能分析数据,帮助开发者优化Java应用的性能。

生成映射文件后,你还可以使用Brendan Gregg的FlameGraph工具来生成火焰图,这是一种直观展示性能瓶颈的图表。

FlameGraph是一个强大的工具,用于可视化和分析软件性能,特别是CPU使用情况。对于Java项目,结合perfperf-map-agent和FlameGraph,可以有效地识别性能热点和瓶颈。以下是如何使用这些工具来分析和优化Java项目的步骤:

FlameGraph使用步骤
步骤1:收集性能数据
  1. 确保Java应用以允许性能分析的模式运行。这通常意味着需要使用特定的JVM参数,如-XX:+PreserveFramePointer,以便perf能够获取到更准确的调用栈信息。

  2. 使用perf收集性能数据。找到Java应用的进程ID(PID),然后运行:

    perf record -F 99 -p [PID] -g -- sleep [duration]
    

    其中[PID]是Java进程的ID,[duration]是收集数据的时间长度(秒)。

步骤2:生成映射文件
  1. 安装perf-map-agent。按照之前的指导安装并编译perf-map-agent

  2. 生成映射文件。在Java应用运行的同时,使用perf-map-agentperf生成映射文件:

    cd perf-map-agent/out
    java -cp attach-main.jar:$JAVA_HOME/lib/tools.jar net.virtualvoid.perf.AttachOnce [PID]
    

    这将为perf生成一个映射文件,使其能够解析Java方法名称。

步骤3:生成FlameGraph
  1. 使用perf生成堆栈折叠文件

    perf script > out.perf
    
  2. 使用FlameGraph工具生成火焰图

    cd FlameGraph
    ./stackcollapse-perf.pl ../out.perf > out.folded
    ./flamegraph.pl out.folded > flamegraph.svg
    

    这将生成一个名为flamegraph.svg的火焰图文件,你可以使用任何支持SVG格式的浏览器或图像查看器打开它。
    图片示例linux perf_第1张图片

步骤4:分析火焰图
  • 查看火焰图:火焰图的每一个“火焰”代表一个函数调用栈,其中的宽度表示该函数及其子函数占用CPU时间的比例。
  • 识别热点:火焰图的顶部是CPU使用最多的函数。寻找“宽”的火焰,这些是你的性能热点。
  • 深入分析:从热点函数开始,向下追踪其调用栈,了解性能瓶颈的来源。
步骤5:优化代码
  • 优化热点函数:针对火焰图中识别出的热点函数,考虑算法优化、减少不必要的计算、优化数据结构等方法。
  • 重构代码:如果性能瓶颈是由于不合理的代码结构引起的,考虑重构代码以提高效率。
  • 利用并发:对于可以并行处理的任务,考虑使用Java的并发和多线程功能来提高性能。
5. 结合使用jstack

对于Java应用,jstack是一个非常有用的工具,它可以生成Java线程的堆栈跟踪。通过将perf的输出与jstack的输出相结合,可以更准确地定位性能问题。
jstack 是一个用于生成 Java 线程堆栈跟踪的工具,它可以帮助开发者分析和诊断 Java 应用程序的性能问题,特别是线程相关的问题。以下是如何使用 jstack 分析 Java 项目的步骤:

jstack使用步骤
步骤1:找到 Java 进程 ID

首先,你需要找到正在运行的 Java 应用程序的进程 ID(PID)。你可以使用 jpsps 命令来找到 PID。

jps -l

或者:

ps -ef | grep java
步骤2:生成线程堆栈跟踪

使用 jstack 命令生成指定 Java 进程的线程堆栈跟踪。假设 PID 是 12345,你可以运行:

jstack -l 12345 > thread_dump.txt

这将生成一个包含所有线程堆栈跟踪的文件 thread_dump.txt

步骤3:分析线程堆栈跟踪

打开 thread_dump.txt 文件,分析其中的线程堆栈信息。以下是一些常见的分析方法:

  1. 查找死锁jstack 会自动检测死锁并在输出中报告。如果存在死锁,你会在输出中看到类似 “Found one Java-level deadlock” 的信息。

  2. 分析线程状态:每个线程的堆栈跟踪信息会包含线程的状态(如 RUNNABLEBLOCKEDWAITING 等)。通过分析线程状态,可以了解线程是否在等待资源、被阻塞或正在运行。

  3. 查找热点代码:如果某些线程的堆栈跟踪信息显示它们在相同的方法中花费了大量时间,这些方法可能是性能瓶颈。你可以重点分析这些方法,寻找优化的机会。

  4. 分析锁争用:如果多个线程在等待相同的锁,可能会导致性能问题。通过分析堆栈跟踪信息中的锁信息,可以识别锁争用的情况。

示例分析

以下是一个简单的 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
优化建议
  1. 优化热点代码:对于处于 RUNNABLE 状态的线程,分析其堆栈跟踪信息,找出占用大量时间的方法,并进行优化。

  2. 减少锁争用:对于处于 BLOCKEDWAITING 状态的线程,分析其等待的锁,尝试减少锁的持有时间或使用更细粒度的锁。

  3. 避免死锁:如果检测到死锁,分析相关线程的堆栈跟踪信息,找出死锁的原因,并修改代码以避免死锁。

通过使用 jstack 生成和分析线程堆栈跟踪信息,可以有效地诊断和优化 Java 应用程序的性能问题。

总结

虽然perf主要用于分析底层系统和硬件性能,但通过上述方法,它也可以结合使用perf-map-agentFlameGraphjstack工具,成为分析Java应用性能的有力工具。

你可能感兴趣的:(linux,linux)