用于Java的异步探查器-采样CPU和堆探查器

2020-06-15 09:58:56

这个项目是一个低开销的Java采样分析器,不存在安全点偏差问题。它提供了特定于热点的API来收集堆栈跟踪和跟踪内存分配。该分析器可与OpenJDK、Oracle JDK和其他基于HotSpot JVM的Java运行时协同工作。

硬件和软件性能计数器,如缓存未命中、分支未命中、页面错误、上下文切换等。

在此模式下,分析器收集堆栈跟踪示例,包括Java方法、本机调用、JVM代码和内核函数。

通常的方法是接收perf_events生成的调用堆栈,并将它们与AsyncGetCallTrace生成的调用堆栈进行匹配,以便生成Java和本机代码的准确配置文件。此外,异步事件探查器还提供了一种解决方法,可以在AsyncGetCallTrace失败的某些角落情况下恢复堆栈跟踪。

与将地址转换为Java方法名称的Java代理直接使用perf_events相比,此方法具有以下优势:

可以在较旧的Java版本上运行,因为它不需要-XX:+PReserve veFramePointer,后者只在JDK 8u60和更高版本中可用。

不会引入来自-XX:+PReserve veFramePointer的性能开销,在极少数情况下可能高达10%。

可以将探查器配置为收集分配最大堆内存的调用点,而不是检测消耗CPU的代码。

异步探查器不使用具有重大性能影响的诸如字节码检测或昂贵的DTrace探测器等侵入性技术,也不影响转义分析或阻止诸如分配消除之类的JIT优化。仅测量实际的堆分配。

该分析器具有TLAB驱动的采样功能。它依赖于特定于热点的回调来接收两种通知:

当在新创建的TLAB(火焰图中的水帧)中分配对象时;

这意味着不计算每个分配,而只计算每N kB的分配,其中N是TLAB的平均大小。这使得堆采样非常便宜,并且适合生产。另一方面,收集的数据可能是不完整的,尽管在实践中它通常会反映顶级分配来源。

可以使用-i选项调整采样间隔。例如,-i 500k将在平均分配的500KB空间之后采集一个样本。但是,小于TLAB大小的间隔不会生效。

与使用类似方法的Java Task Control不同,Async-Profiler不需要Java飞行记录器或任何其他JDK商业功能,它完全基于开源技术,与OpenJDK兼容。

堆探查器需要热点调试符号。Oracle JDK已经将它们嵌入到libjvm.so中,但是在OpenJDK构建中,它们通常被放在单独的包中。例如,要在Debian/Ubuntu上安装OpenJDK调试符号,请运行:

在Gentoo上,icedTea OpenJDK包可以使用每个包的设置Feature=";nostrid&34;来保留符号。

-e wall选项告诉异步探查器在给定的时间段内对所有线程进行平均采样,而不考虑线程状态:正在运行、休眠或已锁定。例如,这在分析应用程序启动时间时会很有帮助。

-e ClassName.method Name选项检测给定的Java方法,以便使用堆栈跟踪记录此方法的所有调用。

仅支持非本机Java方法。要评测本机方法,请改用硬件断点事件,例如-e JAVA_JAVA_LANG_Throwable_Fill InStackTrace。

确保JAVA_HOME环境变量指向您的JDK安装,然后运行make。GCC是必修课。生成后,探查器代理二进制文件将位于生成子目录中。此外,可以将代理加载到目标进程的小型应用程序jattach也将编译到build子目录中。

从Linux4.6开始,使用perf_events从非根进程捕获内核调用堆栈需要设置两个运行时变量。您可以使用sysctl或按如下方式设置它们:

要运行代理并向其传递命令,需要提供帮助器脚本profiler.sh.。典型的工作流程是启动Java应用程序,附加代理并开始性能分析,演练您的性能场景,然后停止性能分析。代理的输出,包括分析结果,将显示在Java应用程序的标准输出中。

或者,您可以指定-d(持续时间)参数,以便使用单个命令在固定的时间段内分析应用程序。

默认情况下,性能分析频率为100 Hz(每10ms的CPU时间)。以下是打印到Java应用程序终端的输出示例:

-执行配置文件-总样本数:687未知(本地):1(0.15%)-6790000000(98.84%)ns,679个样本[0]Primes.isPrime[1]Primes.primesThread[2]Primes.access$000[3]Primes$1.run[4]java.lang.Thread.run.。为简洁起见省略了很多输出.。NS百分比样本最多-6790000000 98.84%679Primes.isPrime 40000000 0.58%4__do_softirq.。省略了更多输出.。

这表明最热的方法是Primes.isPrime,导致它的hottestcall堆栈来自Primes.primesThread。

如果您需要在JVM启动时立即分析一些代码,而不是使用profiler.sh脚本,那么可以在命令行上将async-profiler作为代理附加。例如:

代理库通过JVMTI参数接口配置。参数字符串的格式在源代码中描述。profiler.sh脚本实际上将命令行参数转换为该格式。

例如,-e alloc转换为event=alloc,-f profile.svg转换为file=profile.svg,依此类推。但是有些参数是由profiler.sh脚本直接处理的。例如,-d 5导致3个操作:使用启动命令附加分析器代理,休眠5秒,然后使用停止命令再次附加代理。

异步探查器提供开箱即用的火焰图形支持。指定-o svg参数可将分析结果转储为可在所有主流浏览器中立即查看的交互式SVG。此外,如果目标文件名以.svg结尾,则将自动选择SVG输出格式。

Start-以半自动模式开始评测,即Profiler将运行,直到显式调用停止命令。

恢复-启动或恢复已停止的早期评测会话。所有收集的数据仍然有效。分析选项不会在会话之间保留,应重新指定。

列表-显示可用性能分析事件的列表。此选项仍然需要PID,因为受支持的事件可能会因JVM版本而异。

-d N-分析持续时间,以秒为单位。如果未提供Start、Resume、Stopor Status选项,则探查器将在指定的时间段内运行,然后自动停止。示例:./profiler.sh-d 30 8983。

-e事件-分析事件:CPU、Alalloc、Lock、Cache-Misses等。使用列表查看可用事件的完整列表。

在分配分析模式中,每个调用跟踪的顶帧是已分配对象的类,计数器是堆压力(已分配的TLabs或TLAB外的对象的总大小)。

在锁分析模式中,顶部帧是锁/监视器的类,计数器是进入该锁/监视器所需的纳秒数。

-e mem:<;func>;[:rwx]在函数<;func>;设置读/写/执行断点。mem事件的格式与perf-record.execution断点也可以由函数名指定,例如-e malloc将跟踪所有本地malloc函数的调用。

-e trace:<;id>;设置内核跟踪点。可以指定tracepoint符号名称,例如-e syscalls:sys_enter_open将跟踪所有打开的syscall。

-I N-如果N后跟ms(毫秒)、us(微秒)或s(秒),则以纳秒或其他单位设置评测间隔。仅计算CPU活动时间。CPU空闲时不收集任何样本。默认值为10000000(10ms)。示例:./profiler.sh-i 500us 8983

-j N-设置Java堆栈分析深度。如果N大于默认值2048,则此选项将被忽略。示例:./profiler.sh-j 30 8983。

-b N-设置帧缓冲区大小,以缓冲区中应容纳的JavamMethod ID数表示。如果收到有关帧缓冲区大小不足的消息,请将此值从默认值增加。示例:./profiler.sh-b 5000000 8983。

-t-单独配置螺纹。每个堆栈跟踪都将以表示单个线程的帧结束。示例:./profiler.sh-t 8983。

-o fmt-指定分析结束时要转储的信息。FMT可以是以下选项之一:

JFR-以Java任务控制可读的Java飞行记录器格式转储事件。这不需要启用JDK商业功能。

折叠[=C]-以FlameGraph脚本使用的格式转储折叠的调用跟踪。这是调用堆栈的集合,其中每行都是分号分隔的帧列表,后跟一个计数器。

总数-计数器是收集的指标的总值,例如总分配大小。

-I包含给定模式的-X排除过滤器堆栈跟踪。-I定义必须出现在堆栈跟踪中的名称模式,而-X是不能出现在输出的任何堆栈跟踪中的模式。可以多次指定-i和-X选项。模式可以以星号*开始或结束,该星号表示任何(可能为空)字符序列。示例:./profiler.sh-i&39;素数。*';-i';java/*';-X';*Unsafe.park*';8983

--Title Title、--width px、--Height px、--minwidth px、--Reverse-FlameGraph参数。示例:./profiler.sh-f profile.svg--title";示例CPU配置文件";--minwidth 0.5 8983。

-f文件名-要将配置文件信息转储到的文件名。文件名中的%p扩展到目标JVM的PID;%t-扩展到命令调用时的时间戳。示例:./profiler.sh-o已折叠-f/tmp/traces-%t.txt 8983。

--all-user-仅包括用户模式事件。当内核分析受perf_event_paranoid设置限制时,此选项非常有用。--all-kernel是仅包含内核模式事件的对应选项。

--cstack模式-如何遍历本机帧(C堆栈)。可能的模式有FP(帧指针)、LBR(最后一个分支记录,从Linux 4.1开始在Haswell上可用)和no(不收集C堆栈)。

默认情况下,C堆栈显示在CPU、iTimer、Wall-Clock和perf-events配置文件中,Java级别的事件(如alloc和lock)只收集Java堆栈。

-v,--version-打印Profiler库的版本。如果指定了PID,则获取加载到给定进程中的库的版本。

可以从容器内部和主机系统分析在Docker或LXC容器中运行的Java进程。

从主机分析时,PID应该是主机名称空间中的Java进程ID。使用PS AUX|grep java或docker top<;容器>;查找进程ID。

Async-Profiler应该由特权用户从主机运行-它将自动切换到正确的PID/装载名称空间,并更改用户凭据以匹配目标进程。还要确保目标容器可以通过与主机上相同的绝对路径访问libasyncProfiler.so。

默认情况下,Docker容器限制访问perf_event_opensyscall。因此,为了允许在容器内进行性能分析,您需要修改seccomp配置文件,或者使用--security-opt seccomp=unconfined选项将其完全禁用。此外,可能需要--cap-add SYS_ADMIN。

或者,如果无法更改Docker配置,您可以退回到-e定时器配置模式,请参阅故障排除。

在大多数Linux系统上,perf_events捕获最大深度为127帧的调用堆栈。在最新的Linux内核上,可以使用sysctl kernel.perf_event_max_stack或通过写入/proc/sys/kernel/perf_event_max_stack文件来配置它。

Profiler为目标进程的每个线程分配8KB的perf_event缓冲区。在非特权用户下运行时,请确保/proc/sys/kernel/perf_event_mlock_kb值足够大(超过8*个线程)。否则,消息";perf_event mmap FAILED:将打印不允许的操作,并且不会收集本机堆栈跟踪。

不能无懈可击地保证以保证没有其他代码运行的方式将perf_events溢出Signalis传递给Java线程,这意味着在极少数情况下,捕获的Java堆栈可能与捕获的本机(用户+内核)堆栈不匹配。

您将不会看到堆栈上Java帧之前的非Java帧。例如,如果START_THREAD调用JavaMain,然后您的Javacode开始运行,您将看不到结果堆栈中的前两个帧。另一方面,您将看到Java代码调用了非Java框架(用户和内核)。

分析间隔太短可能会导致诸如clone()这样的大型系统调用连续中断,因此它永远不会完成;请参阅#97。解决办法只需增加间隔即可。

如果在JVM启动时未加载代理(通过使用-agentpath选项),强烈建议使用-XX:+UnlockDiagnoticVMOptions-XX:+DebugNonSafepoints JVM标志。如果没有这些标志,探查器仍将正常工作,但结果可能不准确,例如,如果没有-XX:+DebugNonSafepoints,配置文件中很可能不会出现简单的内联方法。在运行时附加代理时,CompiledMethodLoad JVMTI事件启用调试信息,但仅适用于打开事件后编译的方法。

由于HotSpot动态附加机制的限制,Profiler必须由与目标JVM进程所有者完全相同的用户(和组)运行,如果Profiler由不同的用户运行,它将尝试自动更改当前的用户和组。对于root用户,这可能会成功,但对于其他用户则不会,从而导致上述错误。

附加套接字/tmp/.java_pidNNN已删除。使用某些计划脚本自动清理/tmp是一种常见做法。配置清理软件以排除删除.java_pid*文件。如何检查:运行lsof-p pid|grep java_pid如果它列出了套接字文件,但该文件不存在,则这就是描述的问题。

Java进程的/tmp目录在物理上与您的shell的/tmp目录不同,因为Java运行在容器或chroot环境中。JAttach试图自动解决此问题,但它可能缺少执行此操作所需的权限。检查strace Build/jAttach PID属性。

JVM正忙,无法到达安全点。例如,JVM正在进行长期运行的垃圾收集。检查方法:运行KILL-3 PID。健康的JVM进程应该在其控制台中打印线程转储和堆信息。

已建立与目标JVM的连接,但JVM无法加载探查器共享库。请确保JVM进程的用户具有通过完全相同的绝对路径访问libasyncProfiler.so的权限。有关详细信息,请参阅#78。

PERF_EVENT_OPEN()系统调用失败。错误消息被打印到目标JVM的错误流。

如果无法更改配置,则可以退回到-e iTimer分析模式。它类似于CPU模式,但不需要perf_events支持。作为一个缺点,将不会有内核堆栈跟踪。

可能需要安装带有OpenJDK调试符号的软件包。有关详细信息,请参阅分配分析。

JVM共享库不导出gHotSpotVMStructs*符号-显然这不是热点JVM。有时,同样的消息也可能是由错误构建的JDK引起的(参见#218)。在这些情况下,安装JDK调试符号可能会解决问题。

异步探查器无法解析非Java函数名称,因为/proc/[pid]/map中的内容已损坏。已知该问题发生在运行Linux kernel 5.x的Ubuntu时的容器中。这是操作系统错误,请参阅https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1843018.。

输出中的此消息表示没有足够的空间来存储所有调用跟踪。请考虑使用-b选项增加帧缓冲区大小。

输出文件由目标JVM进程写入,而不是由探查器脚本写入。如果文件无法打开(例如,由于缺乏权限),则错误消息将打印到目标进程(JVM控制台)的stderr。