JProfiler是一个用于分析运行JVM内部情况的专业工具。 在开发中你可以使用它,用于质量保证,也可以解决你的生产系统遇到的问题。
JProfiler处理四个主要问题:
方法调用
这通常被称为"CPU分析"。方法调用可以通过不同的方式进行测量和可视化, 分析方法调用可以帮助了解你的应用程序正在做什么,并找到提高其性能的方法。
分配
分析堆上对象的分配、引用链和垃圾回收属于"内存分析"的范畴。 这个功能可以让你解决内存泄漏,总之使用更少的内存,分配更少的临时对象。
线程和锁
线程可以持有锁,例如通过在一个对象上做同步。当多个线程协作时,可能会出现死锁,JProfiler可以为你可视化这种情况。 此外,锁可能被争用,这意味着线程在获得锁之前必须等待。通过JProfiler可以深入了解线程及其各种锁情形。
高层子系统
许多性能问题发生在更高的语义层面。例如,对于JDBC调用,你可能想找出哪条SQL语句是最慢的。 对于这样的子系统,JProfiler提供了"探针",将特定有效载荷附加到调用树。
JProfiler的UI是一个桌面应用程序。你可以以交互的方式实时分析JVM,也可以在不使用UI的情况下,自动化分析。 保存在快照中的分析数据,可以通过JProfiler UI打开。此外,命令行工具和构建工具集成可以帮助你自动分析会话。
下面给出了涉及被分析应用程序、JProfiler UI和所有命令行实用程序的所有重要交互全貌。
"JVM工具接口"(JVMTI)是一个本地接口,分析器使用它来获取信息,并添加钩子来插入自己的Instrumentation。 这意味着至少有部分分析代理必须使用本地代码实现,因此JVM分析器不是独立于平台的。 JProfiler支持一系列平台,这些平台在网站上都有列出。
JVM分析器作为一个本地库实现,它可以在启动时加载,也可以在稍后的某个时间点加载。 要在启动时加载,需要在命令行中添加一个VM参数-agentpath:<本地库的路径>
。 你很少需要手动添加这个参数,因为JProfiler会自动为你添加,例如在IDE集成中,集成向导中或者直接启动JVM。 然而,重要的是知道,这是实现分析的原理。
如果JVM成功加载了本地库,它就会调用库中的一个特殊函数,让分析代理可以初始化自己。 然后JProfiler会打印几条以
为前缀的诊断信息,这样你就知道分析已经激活。 重要的是,如果你传递了-agentpath
VM参数,分析代理要么成功加载,要么JVM不启动。
一旦加载,分析代理就会要求JVMTI通知各种事件,如线程创建或类加载。其中一些事件直接提供分析数据。 使用类加载事件,分析代理在类被加载时对它们进行Instrument,并插入自己的字节码来执行其测量。
JProfiler可以将代理加载到已经运行的JVM中,可以使用JProfiler UI,也可以通过bin/jpenable
命令行工具。 在这种情况下,为了应用要求的Instrumentation,可能需要重新转换大量已经加载的类。
JProfiler代理只收集分析数据。JProfiler UI是单独启动的,并通过Socket连接到分析代理。 这意味着,实际上,被分析的JVM是在本地机器上还是在远程机器上运行无关紧要--分析代理和JProfiler UI之间的通信机制总是相同的。
从JProfiler UI,你可以通知代理记录数据,在UI中显示分析数据,并将快照保存到磁盘。作为UI的替代方案, 你也可以通过MBean来控制分析代理。 使用MBean的命令行工具是bin/jpcontroller
。
然而,控制分析代理的另一种方法是使用一组预定义的触发器和操作。通过这种方式,分析代理可以在无人看管的模式下运行。 这在JProfiler中被称为"离线分析",对于自动分析会话非常有用。
虽然JProfiler UI可以显示实时分析数据,但经常需要保存记录所有分析数据的快照。 快照可以在JProfiler UI中手动保存,也可以通过触发器操作自动保存。
快照可以在JProfiler UI中打开和比较。对于自动化处理,命令行工具bin/jpexport
和bin/jpcompare
可以用来从先前保存的快照中提取数据和创建 HTML 报告。
从正在运行的JVM中获取堆快照的一种低开销方式是使用bin/jpdump
命令行工具。 它使用JVM的内置功能来保存HPROF快照,该快照可以被JProfiler打开,并且不需要加载分析代理。
为Windows和Linux/Unix提供了可执行安装程序,引导你一步步完成安装。 如果检测到以前的安装,安装将被简化。
在macOS上,JProfiler使用了UI应用程序的标准安装步骤:提供了一个DMG文件,你可以通过双击它在Finder中挂载,然后你可以将JProfiler应用程序包拖动到/Applications
文件夹。该文件夹显示为一个符号链接其自身DMG。
在Linux/Unix上,安装程序在下载后是不可执行的,所以在执行它们时必须在前面加上sh
。如果你传递参数-c
,安装程序将执行命令行安装。使用参数-q
Windows和Linux/Unix将执行自动化安装。在这种情况下,你可以传递另一个参数-dir <目录>
,以便选择安装目录。
运行安装程序后,它将保存一个包含所有用户输入的文件.install4j/response.varfile
。你可以获取该文件,通过在命令行传递参数-varfile
执行自动化安装。。
要为自动化安装设置许可信息,请传递-Vjprofiler.licenseKey=
和可选的-Vjprofiler.licenseCompany=
作为命令行参数。如果你有浮动许可,请使用FLOAT:<服务器名或IP地址>
代替许可密钥。
Windows安装包也是ZIP格式,Linux安装包是.tar.gz。命令
tar xzvf filename.tar.gz
将解压.tar.gz压缩包到一个单独的顶层目录中。要启动JProfiler,在解压的目录中执行bin/jprofiler
。在Linux/Unix上,jprofiler.desktop
这个文件可以用来将JProfiler可执行文件集成到你的窗口管理器中。例如,在Ubuntu上,你可以将该桌面文件拖到启动器侧栏中,以便创建一个永久的启动器条目。
JProfiler有两部分。一部分是桌面UI和命令行实用程序,用于操作快照;另一部分是分析代理和命令行实用程序,用于控制分析的JVM。从网站上下载的安装包和存档包含这两部分。
然而,对于远程分析,在远程端你只需要安装分析代理。 虽然你可以简单地在远程机器上解压缩JProfiler发行版存档,但你可能希望限制所需文件的数量,特别是在自动部署时。此外,分析代理可以自由重分发,所以你可以将它与你的应用程序一起发布,或者安装在客户的机器上进行故障排除。
要获得带有分析代理的最小包,远程集成向导会向你显示相应代理存档的下载链接,以及所有支持平台的代理存档的下载页面。 在JProfiler GUI中,调用会话->集成向导->新服务器/远程集成,选择"远程"选项,然后继续到远程安装目录步骤。
指定JProfiler版本的HTML概览页的URL是
https://www.ej-technologies.com/download/jprofiler/agent?version=14.0
单一代理存档的下载网址格式是
https://download.ej-technologies.com/jprofiler/jprofiler_agent__14_0.
其中,platform
对应于bin
目录中的代理目录名称, extension
在 Windows 上是zip
, 在 macOS 上是.tgz
,在 Linux/Unix 上是.tar.gz
。 对于 Linux, x86和x64分组到一块儿了,所以Linux x64的URL是
https://download.ej-technologies.com/jprofiler/jprofiler_agent_linux-x86_14_0.tar.gz
代理存档包含所需的本地代理库以及jpenable
、jpdump
和jpcontroller
可执行文件。存档中的可执行文件要求的最低版本为 Java 6,分析代理可以使用 Java 5 或更高版本。
你在远程机器上提取代理存档后看到的子目录如下,它们是各目标平台完整JProfiler安装的子集。
要想对一个JVM进行分析,JProfiler的分析代理必须被加载到该JVM中。这可以通过两种不同的方式实现: 通过在启动脚本中指定一个-agentpath
VM参数,或者通过Attach API将代理加载到已经运行的JVM中。
两种模式JProfiler都支持。添加VM参数是分析的首选方式,集成向导、IDE插件和从JProfiler内启动JVM的会话配置都使用这种方式。 Attach既可在本地使用,也可通过SSH远程使用。
了解加载分析代理的VM参数的构成很有用。-agentpath
是JVM提供的一个通用VM参数,用于加载任何一种使用JVMTI接口的本地库。 由于分析接口JVMTI是一个本地接口,所以分析代理必须是一个本地库。这意味着你只能在 明确支持的平台上进行分析。 32位和64位JVM也需要不同的本地库。另外,Java代理是用-javaagent
VM参数加载的,并且只能访问有限的功能。
在-agentpath:
后面附加本地库的完整路径名。有一个等效参数-agentlib:
, 你只需要指定特定平台的库名,但必须确保库路径中包含该库。在库的路径之后,你可以添加一个等号,并向代理传递选项,用逗号隔开。例如,在Linux上,整个参数看起来可能像这样:
-agentpath:/opt/jprofiler10/bin/linux-x64/libjprofilerti.so=port=8849,nowait
第一个等号将路径名与参数分开,第二个等号是参数port=8849
的一部分。 这个常用参数定义了分析代理监听来自JProfiler GUI连接的端口,实际上8849是默认端口,所以也可以省略这个参数。 如果你想在同一台机器上分析多个JVM,必须分配不同端口, 不过IDE插件和本地启动的会话会自动分配这个端口,对于集成向导,你必须显式选择端口。
第二个参数nowait
告诉分析代理不要在启动时为了等待JProfiler GUI连接而阻塞JVM。 启动时的阻塞是默认的,因为分析代理不是以命令行参数的形式接收其分析设置,而是从JProfiler GUI或配置文件接收。 命令行参数仅用于引导分析代理,告诉它如何启动和传递调试标记。
默认情况下,JProfiler代理将通信Socket绑定到所有可用的网络接口。如果出于安全原因不希望这样做, 你可以添加选项address=[IP地址]
以便选择指定接口,或loopback
只监听来自本地机器的请求。对于通过JProfiler UI启动的或通过IDE集成的JVM,会自动添加loopback
就像IDE中的"Run configurations",你可以直接在JProfiler中配置本地启动的会话。 你指定类路径、主类、工作目录、VM参数和参数,JProfiler会为你启动会话。所有JProfiler附带的演示会话都是本地启动的会话。
一个特殊的启动模式是"Web启动",你选择JNLP文件的URL,JProfiler将启动一个JVM对其进行分析。 这个功能支持OpenWebStart,不支持Java9之前的Oracle JRE 的传统WebStart。
本地启动的会话可以通过调用主菜单中的转换向导会话->转换向导转换为独立的会话。 应用会话转换为远程会话简单地创建一个启动脚本,并将-agentpath
VM参数插入到Java调用中。 应用会话转换为离线会话会为离线分析创建一个启动脚本,这意味着配置在启动时被加载,不需要JProfiler GUI。 应用会话转换为重分发会话做了同样的事情,但是在它旁边创建了一个目录jprofiler_redist
, 其中包含了分析代理和配置文件,这样你就可以把它发送到没有安装JProfiler的其他机器上。
如果被分析应用程序是你自己开发,可以考虑使用IDE集成而不是启动会话。这样会更方便,并给你提供更好的源代码导航。 如果应用程序不是你自己开发,但已经有了启动脚本,可以考虑使用远程集成向导。它将告诉你必须添加到Java调用中的确切VM参数。
JProfiler的集成向导可以处理许多知名的第三方容器,这些容器具有启动脚本或配置文件,可以通过编程修改以包含额外的VM参数。 对于某些产品,可以生成启动脚本,其中VM参数作为参数或通过环境变量传递。
在所有用例中,你必须定位第三方产品的一些特定文件,这样JProfiler才有需要的上下文来执行其修改。一些通用向导只给你必须做什么才能启用分析的操作指南。
每个集成向导的第一步是选择在本地机器上还是在远程机器上进行分析。在本地机器的情况下,你需要提供的信息更少,因为JProfiler已经知道平台,JProfiler的安装位置和它的配置文件的位置。
一个重要的决定是上面讨论过的"启动模式"。默认情况下,分析设置是在启动时从JProfiler UI中传输的,但你也可以告诉分析代理让JVM立即启动。在后一种情况下,一旦JProfiler GUI连接,就可以应用分析设置。
然而,你也可以通过分析设置配置同步步骤指定一个配置文件,这样更高效。 这种方式的主要问题是,每次你在本地编辑分析设置,必须要与远程端同步配置文件。 最优雅的方法是在远程地址步骤通过SSH连接到远程机器,然后通过SSH自动传输配置文件。
在集成向导最后,将创建一个会话,该会话将开始分析,并且--在非通用情况下--还会启动第三方产品,如应用服务器。
外部启动脚本由会话设置对话框中应用程序设置选项卡上的执行启动脚本和 执行停止脚本 选项处理,可以通过选择浏览器中打开URL复选框显示URL。这里也是你可以更改远程机器地址和配置同步选项的地方。
集成向导所有处理的情况是被分析JVM运行在远程机器上。但是,当配置文件或启动脚本必须修改时,你必须把它复制到本地机器上,然后把修改后的版本传回远程机器上。 直接在远程机器上运行命令行工具jpintegrate
,让它在相应位置进行修改可能更方便。 jpintegrate
需要JProfiler的完整安装,并且与JProfiler GUI的JRE要求相同。
当启动远程分析会话时发生错误,请参见故障排除指南,查看可用于解决问题的步骤清单。
分析应用程序最方便的方式是通过IDE集成。如果你通常在开发过程中从IDE启动你的应用程序,IDE已经有了所有需要的信息, JProfiler插件可以简单地添加VM参数进行分析,必要时启动JProfiler,并将被分析JVM连接到JProfiler主窗口。
所有的IDE集成都包含在JProfiler安装的integrations
目录中。原则上, 该目录中的归档可以通过各自IDE的插件安装机制手动安装。但是,安装IDE集成的首选方式是调用主菜单中的会话->IDE集成。
IDE中的分析会话在JProfiler中没有自己的会话条目,因为这样的会话无法从JProfiler GUI中启动。根据IDE中的设置,Profiling设置持久化在每个项目或每个运行配置的基础上。
当连接到IDE,JProfiler在工具栏中会显示一个窗口切换器,可以很容易跳回IDE中的相关窗口。所有显示源代码操作现在直接在IDE中显示源码,而不是JProfiler中的内置源码查看器。
IDE集成将在后面的章节中详细讨论。
对一个JVM进行分析,你不一定预先就做决定,通过JProfiler中的Attach功能,你可以选择一个正在运行的JVM,动态加载分析代理。 虽然Attach模式很方便,但它也有一些缺点你应该知道:
您必须从正在运行的 JVM 列表中辨认出您要分析的JVM。如果在同一台机器上运行者很多JVM,这有时会很棘手。
有额外的开销,因为可能需要重新定义许多类以添加instrumentation。
JProfiler中的一些功能在Attach模式下无法使用。这主要是因为JVMTI的一些功能只有在JVM被初始化时才能开启, 而在JVM生命周期的后期阶段无法使用。
有些功能需要对所有类中的很大比例进行instrumentation。在一个类正在加载的时候进行instrumentation花费成本很低, 而在类被已经加载后添加instrumentation就不低了。当你使用attach模式时,这样的功能默认是被禁用的。
Attach功能支持OpenJDK JVM,版本6或更高版本的Oracle JVM,最近的OpenJ9 JVM(8u281+, 11.0.11+ or Java 17+)或 基于这些发布版本的IBM JVM。不得为JVM指定VM参数 -XX:+PerfDisableSharedMem
和-XX:+DisableAttachMechanism
。
JProfiler启动中心的快速Attach选项卡列出了所有可以被分析的JVM。 列表项的背景颜色指示了是否已经加载了分析代理,当前是否连接了JProfiler GUI,或者是否已经配置了离线分析。
当你启动分析会话时,可以在会话设置对话框中配置分析设置。 当重复分析同一进程时,你不会希望重复输入相同的配置,所以当关闭用快速Attach功能创建的会话时,可以保存一个持久会话。 你下次要分析该进程时,从打开会话选项卡启动保存的会话而不是快速Attach选项卡。你仍然必须选择一个正在运行的JVM,但分析设置与你之前已经配置的相同。
JVM中的Attach API要求调用进程与你想要Attach的进程以相同用户身份运行,所以JProfiler显示的JVM列表仅限于当前用户。 不同用户发起的进程大多是服务。对于Windows,Linux和基于Unix的平台,Attach到服务的方式有所不同。
在Windows上,Attach对话框有一个 显示服务按钮,会列出所有本地运行的服务。 JProfiler会启动桥接可执行文件,以便能够Attach到这些进程上,无论它们通过什么用户运行。
在Linux上,JProfiler支持通过PolicyKit在UI中直接切换用户,PolicyKit是大多数Linux发行版本的一部分。 通过点击attach对话框中的切换用户,你可以输入不同的用户名并通过系统密码对话框进行认证。
在基于Unix的平台上,包括macOS,你可以使用su
或sudo
以不同用户身份执行命令行工具jpenable
,这取决于你的Unix变种或Linux发行版。 macOS和基于Debian的Linux发行版(如Ubuntu)上,使用sudo
。
使用sudo,调用
sudo -u userName jpenable
使用su,所需的命令行是
su userName -c jpenable
jpenable
会让你选择 JVM,并告诉你分析代理正在监听的端口。 然后在启动中心的快速Attach选项卡上,你可以选择选项在另一台计算机上, 并配置一个直接连接到localhost和给定的分析端口。
分析最苛刻的设置是远程分析--JProfiler GUI在本地机器上运行,而分析的JVM在另一台机器上运行。 对于将-agentpath VM参数传递给分析的JVM的设置,你必须在远程机器上安装JProfiler,并在本地机器上设置一个远程会话。 有了JProfiler中的远程Attach功能,就不需要这样的修改,你只需要SSH凭证就可以登录到远程机器上。
SSH连接使JProfiler能够上传"安装JProfiler"帮助主题中讨论的代理包, 并在远程机器上执行包含的命令行工具。你不需要在你的本地机器上设置SSH,JProfiler有自己的实现。最简单的设置,你只需设置主机、用户名和认证。
通过SSH连接,JProfiler可以自动发现正在运行的JVM,或者连接到分析代理已经在监听的特定端口。对于后一种情况, 可以如上所述在远程机器上使用jpenable
或jpintegrate
, 准备一个特别 JVM 以进行分析。然后,可以配置SSH远程Attach,直接连接到配置好的分析端口。
自动发现将列出远程机器上所有以SSH登录用户启动的JVM。在大多数情况下,这不是启动你想要分析的服务的用户。 因为启动服务的用户通常是不允许SSH连接的,所以JProfiler添加了切换用户超链接,让你使用sudo
或su
切换到那个用户。
在复杂的网络拓扑结构中,有时你不能直接连接到远程机器。在这种情况下,你可以告诉JProfiler在GUI中用一个多跳SSH隧道进行连接。 在SSH隧道的最后,你可以进行一个直接的网络连接,通常是"127.0.0.1"。
HPROF快照只能为以SSH登录用户启动的JVM生成。这是因为HPROF快照需要一个用启动JVM的用户访问权限写入的一个中间文件。 出于安全原因,不可能将文件权限转移给SSH登录用户进行下载。 对于完整的分析会话不存在这样的限制。
Docker容器通常没有安装SSH服务器,虽然你可以在Docker容器中使用jpenable, 但除非你在Docker文件中指定,否则分析端口将无法从外部访问。
在JProfiler中,你可以通过在快速Attach对话框中选择Docker容器,来Attach到,在Windows或macOS的本地Docker Desktop安装中运行的JVM。 在快速连接对话框中选择Docker容器。默认情况下,JProfiler会自动检测 docker
可执行文件的路径。 另外,你也可以在常规设置对话框中的 "外部工具 "选项卡中配置。
在你选择容器后,所有在Docker容器内运行的JVM都会被显示出来。当你选择一个JVM时。 JProfiler将使用Docker命令在选定的容器中自动安装分析代理,为分析准备JVM,并将分析协议以隧道方式连接到外部。
对于远程Docker安装,你可以使用SSH远程Attach功能,然后在远程机器上选择一个Docker容器。 如果登录用户不在docker组中,你可以先按照上述方法切换用户。
通过远程Attach对话框中的选择容器超链接,你可以选择一个运行的Docker容器,并显示运行在其中的所有JVM。
为了对运行在Kubernetes集群上的JVM进行分析,JProfiler使用kubectl
命令行工具, 既可以发现pod和容器,也可以连接到容器,列出其JVM 并最终连接到选定的JVM。
kubectl
命令行工具也许在你的本地计算机上可用,或者在一个你有SSH访问权远程机器上可用。 这两种情况JProfiler都支持。对于本地安装,JProfiler将自动尝试检测kubectl
的路径, 但你可以在常规设置对话框中的”外部工具“选项卡配置一个明确的路径。
JProfiler会以一个三层树列出所有检测到的容器。最顶部是命名空间节点,其字节点包含检测到的pod。 叶节点是容器本身,必须选择其中之一以便进一步选择一个运行的JVM。
kubectl
可能需要额外的命令行选项进行验证,以便能够连接到Kubernetes集群。 这些选项可以在容器选择对话框的顶部输入。因为这些选项可能是敏感信息,所以只有在你明确勾选,要记住这些内容的,跨重启记住复选框, 这些信息才会被保存到磁盘。取消选择该复选框将立即清除任何先前保存的信息。
在 JVM 选择表中,显示的进程名称是分析的 JVM 的主类及其参数。对于exe4j或install4j生成的启动程序,会显示可执行文件的名称。
如果您希望自己设置显示的名称,例如,因为您有几个具有相同主类的进程,否则将无法区分, 您可以设置VM参数-Djprofiler.displayName=[name]
。如果名称包含空格, 请使用单引号:-Djprofiler.displayName='My name with spaces'
,必要时用双引号引住整个VM参数。 除了-Djprofiler.displayName
外,JProfiler 还可以识别-Dvisualvm.display.name
。
分析器的主要目的是记录来自各种来源的运行时数据,这些数据对解决常见的问题很有用。这项任务的主要问题是,运行JVM会以极快的速度生成这些数据。 如果分析器总是记录所有类型的数据,就会产生不可接受的开销,或者很快就会耗尽所有可用的内存。此外,你通常希望记录围绕一个特定用例相关的数据, 不希望看到任何不相关的活动。
这就是为什么JProfiler提供了细粒度的机制来控制你真正感兴趣的信息的记录。
从分析器的角度来看,问题最少的数据形式是标量值,例如活跃线程数或打开的JDBC连接数。 JProfiler可以以固定肉眼可见的频率-通常是每秒一次对这类值进行采样,并向你展示随时间的演变。 在JProfiler中,显示此类数据的视图称为遥测。 大多数遥测数据总是会被记录,因为这种测量的开销和内存消耗都很小。如果数据被记录了很长时间,旧的数据点会被合并, 这样内存消耗就不会随着时间线性增长。
还有一些参数化的遥测,比如每个类的实例数量。额外的维度使得永久的时间记录变得不可持续。 你可以告诉JProfiler对一些选定类而不是每个类的实例数记录遥测。
继续前面的例子,JProfiler能够向你展示所有类的实例计数,但没有顺序信息。 这就是"所有对象"视图,它在表格中每个类显示一行,更新视图的频率低于每秒一次,可能会根据测量造成的开销大小自动调整。 确定所有类的实例计数成本比较高,而且堆上的对象越多,需要的时间越长。JProfiler限制了"所有对象"视图的更新频率, 因此在极端情况下,测量的开销永远不会超过10%。 你可以冻结视图来暂时停止记录。 另外,如果视图不活动,数据将不会被记录,也不会有相应的开销。
有些测量可以捕获类枚举值,例如线程当前所处的执行状态,这种测量可以以彩色时间线的形式显示,比数值遥测消耗的内存少很多。 在线程状态的例子中,"线程历史"视图显示了JVM中所有线程的时间顺序。就像数值遥测一样,旧的值会被合并,并变得更加粗粒度,以减少内存消耗。
如果你对某个时间间隔内被分配的实例计数感兴趣,JProfiler必须跟踪所有分配。与"所有对象"视图相反, JProfiler可以遍历堆中的所有对象以按需获取信息,而跟踪单次分配则需要为每次对象分配执行额外的代码。 这使得它成为一种非常昂贵的测量方法,会显著改变被分析应用程序的运行时特征,例如性能热点,特别是当你分配了许多对象时。 这就是为什么必须显式启动和停止分配记录的原因。
相关的记录视图初始会显示一个空页面,上面有一个记录按钮。也可以在工具栏中发现同样的记录按钮。
分配记录不仅记录分配的实例数量,还记录分配堆栈跟踪。在内存中保留每个分配记录的堆栈跟踪会造成过多的开销, 所以JProfiler将记录的堆栈跟踪累积成一棵树。这样做还有一个好处,就是你可以更容易解释数据。 但是,失去了时间顺序相关数据,而且没有办法从数据中提取指定时间范围。
分配记录只能测量对象的分配位置,没有对象之间的引用信息。任何需要引用的内存分析,如解决内存泄漏,要在堆遍历器中完成。 堆遍历器会对整个堆生成一个快照并对其分析。这是一个侵入性的操作,会暂停JVM-可能会暂停很长时间-并且需要大量的内存。
更轻量级的操作是在开始一个用例之前标记堆上的所有对象,这样在稍后生成堆快照时就可以找到所有新分配的对象。
JVM有一个特殊的触发器,用于将整个堆转储到一个以旧HPROF分析代理命名的文件中。这与分析接口无关,也不在其限制下操作。 由于这个原因,HPROF堆转储速度更快,使用的资源更少。缺点是,当你在堆遍历器器中查看堆快照时,你将没有与JVM的实时连接, 并且一些功能不可用。
测量方法调用耗费的时间是一种可选的记录,就像分配记录。方法调用会累积成一棵树,有各种视图从不同的角度显示记录的数据,比如调用图。 这类数据的记录在JProfiler中称为"CPU记录"。
在特殊情况下,查看方法调用的时间顺序可能非常有用,特别是当涉及到多个线程时。对于这些特殊情况, JProfiler提供了"调用跟踪器"视图。该视图有一个单独的记录类型,不与更普遍的CPU记录绑定。 需要注意的是,调用跟踪器产生的数据太多,对解决性能问题没有用处,它只是为了一种特殊的调试形式使用。
调用跟踪器依赖于CPU记录,必要时自动开启。
另一个自带记录功能的专门视图是"复杂度分析"。它只测量选定方法的执行时间,不需要启用CPU记录。 它的附加数据轴是一个方法调用的算法复杂度数值,你可以用脚本计算。 这样,你可以测量一个方法的执行时间究竟多大程度取决于它的参数。
为了分析线程为什么等待或阻塞,必须记录相应的事件。这类事件的发生率差异很大。 对于线程频繁协调任务或共享公共资源的一个多线程程序来说,可能会有大量的此类事件。 这就是为什么默认情况下不记录这种时间顺序严谨的数据。
当您打开Monitor记录时,"锁状态历史图"和"Monitor历史"视图将开始显示数据。
为了消除噪音和减少内存消耗,非常短的事件不会被记录。通过视图设置,您可以调整这些阈值。
探针显示JVM中更高层次的子系统,如JDBC调用或文件操作。默认情况下,不记录探针,你可以为每个探针分别切换记录。 有些探针会增加很少或没有开销,有些探针会创建大量数据,这取决于你的应用程序正在做什么以及探针是如何配置的。
就像分配记录和方法调用记录,探针数据是累计的,除了时间线和遥测之外,其他的时间顺序信息都被丢弃了。 然而,大多数探针还有一个"事件"视图,允许你检查单个事件。这潜在的增加了一个大开销,和一个单独的记录动作。 该记录操作的状态是持久的,所以当你切换探针记录时,如果你之前已经打开了它,那么相关的事件记录也会被切换。
JDBC探针有第三个记录操作,用于记录JDBC连接泄漏。只有当你真正试图调查这样的问题时,才会产生寻找连接泄漏的相关开销。 就像事件记录操作,泄漏记录操作的选择状态是持久的。
在很多情况下,你想通过一次点击开始或停止各种记录。逐个访问所有相应视图,逐个切换记录按钮是不切实际的。 这就是为什么JProfiler有记录分析。记录分析可以通过点击工具栏中的开始记录按钮创建。
记录分析定义了一个可以原子化激活的特定记录组合。JProfiler试图让你对所选记录产生的开销有一个粗略的印象, 并试图阻止有问题的组合。特别是,分配记录和CPU记录并不能很好地组合在一起,因为CPU数据的计时会被分配记录严重扭曲。
您可以在会话运行时随时激活记录分析。记录分析不是叠加的,它们会停止所有未包含在记录分析中的记录。 通过停止记录按钮,您可以停止所有的记录,无论它们是如何被激活的。 要查看当前激活的记录有哪些,请将鼠标悬停在状态栏的记录标签上。
也可以直接在启动分析时激活一个记录分析。在"会话启动"对话框中有一个 初始化记录分析下拉菜单。 默认情况下,不选择任何记录分析,但如果你需要JVM启动阶段的数据,这里是配置所需记录的地方。
有时你想在某个特定条件发生时开始记录。JProfiler有一个定义触发器的系统, 可以执行一系列操作。可用的触发操作也包括改变激活记录。
例如,你可能想要仅当某个特定方法执行时才启动一个记录。在这种情况下,你可以进入会话设置对话框,激活 触发器设置选项卡, 并为该方法定义一个方法触发器。对于操作配置,您有许多不同的记录动作可用。
"开始记录"操作可以控制那些没有任何参数的记录。通常情况下,当您停止和重新开始记录时,所有之前记录的数据都会被清除。 对于"CPU数据"和"分配数据"的记录,你也可以选择保留之前的数据,并在多个时间段内继续累加。
通过使用上下文菜单中的"添加方法触发器"操作,可以在调用树中方便地添加方法触发器。 如果在同一个会话中已经有了方法触发器,可以选择在现有的触发器上添加方法拦截。
默认情况下,当JVM被启动进行分析时,触发器是激活的。有两种方法可以在启动时禁用触发器: 你可以在触发器配置中单独禁用它们,或者取消选择会话启动对话框中的启动时启用触发器复选框, 在实时会话期间,你可以通过选择菜单中的分析->(启用|禁用)触发器或 点击状态栏中的 触发器记录状态图标启用或禁用所有触发器。
有时,你需要同时为触发器组切换触发激活。这可以通过为感兴趣的触发器指定相同的组ID, 然后调用菜单中的分析->启用触发器组实现。
JProfiler有一个命令行可执行文件,用于控制已经被分析的任何JVM中的记录。 jpcontroller要求发布JProfiler MBean,否则它将无法连接到被分析的JVM。这仅限于分析代理已经接收到分析设置。 如果没有分析设置,代理将不知道到底要记录什么。
必须满足以下条件之一:
你已经通过一个JProfiler GUI连接到JVM了。
分析的JVM通过-agentpath
VM参数启动,该参数包含nowait
和config
参数。 这和集成向导中的立即启动模式和配置同步步骤的在启动时应用配置选项相对应 。
JVM已经使用jpenable
可执行文件为分析析做了准备,并且指定了-offline
参数。 更多信息请参见jpenable -help
的输出。
特别是,如果分析的JVM只使用nowait
标志启动,jpcontroller
将无法工作。 在集成向导中,配置同步步骤中与JProfiler GUI连接时应用配置选项将配置这样一个参数。
jpcontroller为你提供了一个循环的多级菜单,可以显示所有的记录及其参数。你也可以用它保存快照。
还有另一种启动分析的方式是通过API。在分析的VM中,你可以调用com.jprofiler.api.controller.Controller
类以编程方式开始和停止记录。请参阅离线分析章节,了解更多信息以及如何获得包含控制器类的工件。
如果你想控制不同JVM中的记录,你可以访问被分析JVM中也被jpcontroller
使用的相同MBean。 设置MBean的编程方式使用有点复杂,涉及很多规则,所以JProfiler附带了一个例子,你可以重复使用。 查看文件api/samples/mbean/src/MBeanProgrammaticAccessExample.java
。 它在另一个被分析JVM中记录了5秒钟的CPU数据,并将快照保存到磁盘上。
到目前为止,我们只研究了JProfiler GUI从正在被分析的JVM内部运行的分析代理获取数据的实时会话情形。 JProfiler还支持快照,所有分析数据都被写入了快照文件。这种方式在如下几种情景有优势:
你以自动化方式记录分析数据,例如作为测试的一部分,这样情况就不可能与JProfiler GUI连接。
你想比较来自不同分析会话的分析数据或查看旧的记录。
你想与他人共享分析数据。
快照包含所有记录的数据,包括堆快照。为了节省磁盘空间,快照经过压缩,除了为允许直接内存映射必须保持未压缩的堆遍历器数据。
当你分析实时会话时,可以使用保存快照工具栏按钮创建快照。 JProfiler从远程代理提取所有分析数据,并将其保存到扩展名为".jps"的本地文件中。 你可以在实时会话过程期间保存多个此类快照。这些快照不会自动打开,你可以继续进行分析。
保存的快照会自动添加到 文件->最近的快照菜单,所以你可以方便地打开刚刚保存的快照。 当实时会话仍在运行时打开一个快照,你可以选择终止实时会话或打开另一个JProfiler窗口。
当你使用JProfiler中的快照比较功能时,快照列表由你为当前实时会话保存的所有快照构成。这使得它很容易比较不同的用例。
通常,你可以通过调用主菜单中的会话->打开快照或在文件管理器中双击快照文件打开快照。 JProfiler的IDE集成也支持通过IDE他们自己一般的打开文件操作打开JProfiler快照。 在这种情况下,你将使用IDE的源代码导航,而不是内置的源代码查看器。
当你打开快照时,所有的记录操作都被禁用,只有带有记录数据的视图可用。要查看记录了哪些类型的数据,请将鼠标悬停在状态栏中的记录标签上。
对于实时会话,所有的分析数据都驻留在被分析JVM的进程中,所以当被分析的JVM被终止时,JProfiler中的分析会话也会被关闭。 要想在JVM退出时继续分析,您有两个选项,这两个选项都可以在会话启动对话框中激活。
你可以防止JVM实际退出,只要JProfiler GUI被连接,就可以人为地保持它存活。 当你从IDE中分析一个测试用例,并希望在IDE的测试控制台中看到状态和总时间时,这种方式可能是不可取的。
你可以要求JProfiler在JVM终止时保存一个快照,并立即切换到该快照。 该快照是临时的,将在你关闭会话时被丢弃,除非你首先使用了保存快照操作。
自动分析会话的最终结果总是一个快照或一系列快照。在触发器中,你可以添加一个"保存快照"操作,将快照保存在运行分析JVM的机器上。 当该触发器在实时会话期间运行时,快照也会保存在远程机器上,并且可能不包含已经传送到JProfiler GUI的部分数据。
用触发器保存快照有两种基本策略:
对于测试用例,在"JVM启动"触发器中开始记录,并添加一个"JVM退出"触发器,在JVM终止时保存快照。
对于特殊情况,如"CPU负载阈值"触发器,或使用"定时器触发器"进行周期性的分析,在记录一些数据后保存快照,中间有一个"休眠"动作。
在生成堆快照会产生太多开销或消耗太多内存的情况下,你可以使用JVM内置提供的HPROF堆快照功能。 因为这个操作不需要分析代理,所以对于分析在生产中运行的JVM内存问题很有用。
通过JProfiler,有三种方式可以获得这种快照:
对于实时会话,JProfiler GUI在主菜单中提供了一个触发HPROF堆转储的操作。
JProfiler有一个特殊的"内存溢出异常"触发器,当OutOfMemoryError
被抛出时,会保存一个HPROF快照。 这对应于HotSpot JVM所支持的
-XX:+HeapDumpOnOutOfMemoryError
VM参数。
JDK中的jmap可执行文件 可用于从运行的JVM中提取HPROF堆转储。
JProfiler包含的命令行工具jpdump
,比jmap更通用。 它可以让你选择一个进程,可以连接到Windows上作为服务运行的进程,对32位/64位混合的JVM没有问题,并且自动编号HPROF快照文件。 使用-help
选项执行它,以了解更多信息。
JProfiler完全支持打开由Java飞行记录器(JFR)保存的快照。在这种情况下,UI明显不同,根据JFR的功能进行了调整。 查看JFR帮助主题获取更多详细信息。
分析的一个方面是随时间监控标量测量,例如,使用的堆大小。 在JProfiler中,这种数据图被称为遥测。 观察遥测可以让你更好地理解分析的软件,你可以通过不同的测量关联重要事件,如果注意到有异常的行为, 可能会促使你使用JProfiler中的其他视图进行更深入的分析。
在JProfiler UI的"VM遥测"部分,默认记录一些遥测。 对于交互式会话,它们始终处于启用状态。 有些遥测要求记录一些特殊类型的数据,在这种情况下,遥测中会显示一个记录操作。
要想在同一时间轴上比较多个遥测,概览中通过可配置行高显示了多个小比例的遥测。点击遥测标题可激活完整遥测视图。 视图中遥测的顺序可能不是你想要的,例如,你可能想要将选定遥测并排对比。在这种情况下,你可以在视图中拖拽它们重新排序。
完整视图会显示一个带有当前值的图例,可能比概览中可以看到的选项更多。 例如,"内存"遥测允许你选择单个内存池。
JProfiler有大量探针,这些探针可以记录JVM中高级系统和重要框架中的事件。 探针的遥测显示在相应的探针视图中。如果要将这些遥测与系统遥测进行比较,你可以将选定的遥测添加到顶层(top-level)遥测部分。 在工具栏中,选择
添加遥测->探针遥测 然后选择一个或多个探针遥测。
每个添加的遥测,在遥测部分都会有它自己的视图,也会在概览中显示。
一旦一个探针被添加,只有记录了探针数据,数据才会被显示。如果没有,遥测描述中包含了一个开始记录的内嵌按钮。
探针遥测的上下文菜单除了一些记录操作,还有一个显示相应探针视图的操作。
类似于探针视图,记录的对象的VM遥测依赖于内存记录,也有一个记录按钮和一个类似的上下文菜单。
最后,还有一些"跟踪"遥测,可以监控在另一个视图中选择的标量值。 例如,类跟踪器视图允许你选择一个类,并监控随时间推移其实例数的变化。 此外,每个探针都有一个监控选定热点或控制对象的"跟踪器"视图。
JProfiler维护着一个在所有遥测中显示的书签列表。 在交互式会话中,你可以在当前时间通过点击添加书签工具栏按钮 或使用上下文菜单中的在此添加书签功能来添加书签。。
书签不仅可以手动创建,还可以通过记录动作自动添加,以指示特定记录的开始和结束。 通过触发器操作或控制器API,你可以以编程方式添加书签。
书签有颜色、线条样式,还有显示在工具提示中的名称。你可以编辑现有的书签并更改这些属性。
如果在遥测中右键点击多个书签太不方便,可以使用菜单中分析->编辑书签操作,会显示一个标签列表, 这里你也可以将书签导出为HTML或CSV。
有两种方式你可以添加自己的遥测:可以在JProfiler UI中写一个脚本来提供一个数值,或者选择一个数值MBean属性。
要添加自定义遥测,请单击配置遥测工具栏按钮,该按钮在"遥测"部分可以看到。 在脚本遥测中,你可以访问当前JProfiler会话的类路径中配置的所有类。 如果一个值不能直接使用,在你的应用程序中添加一个静态方法,你可以在这个脚本中调用。
上面的例子显示了对一个平台MBean的调用。用MBean遥测来绘制MBeans的标量值更方便。 在这里,一个MBean浏览器允许你选择一个合适的属性。属性值必须是数值。
你可以将几条遥测线捆绑到一个遥测。这就是为什么配置分为两部分:遥测本身和遥测线。 在遥测线中,你只需编辑数据源和行标题,在遥测线中,你可以配置单位、刻度和应用于所有包含的测线的堆叠。
在堆叠遥测中,累积单条遥测线,可以显示面积图。刻度因子对于将一个值转换为支持的单位很有用。 例如,如果数据源报告的是kB,问题是JProfiler中没有匹配的"kB"单位。 如果你将刻度因子设置为-3,则值将被转换为字节,通过选择"字节"作为遥测的单位,JProfiler将自动在遥测中显示适当的合计单位。
自定义遥测按照它们被配置的顺序添加在"遥测"部分的最后。 要想对它们重新排序,在视图中将其拖拽到想要的位置。
乍一看,遥测似乎会随着时间的推移而线性地消耗内存。然而,JProfiler合并旧值,并使它们逐渐变得更粗粒度,以限制每个遥测所消耗的内存总量。
遥测的CPU开销受限于它们的值每秒只被轮询一次。 对于标准遥测,这种数据收集没有额外的开销。 对于自定义遥测,开销由底层脚本或MBean控制。
当JProfiler测量方法调用的执行时间和它们的调用堆栈时,我们称之为"CPU分析"。 这些数据以多种方式呈现。根据你试图解决的问题,一种或另一种展示方式将是最有帮助的。 默认不记录CPU数据,你必须打开CPU记录才能采集到有趣的用例。
跟踪所有的方法调用及其调用栈会消耗相当大的内存,短时间内就会耗尽所有内存。 另外,在一个繁忙的JVM中,很难直观获得方法调用的数量。通常情况下,这个数字是如此之大,以至于定位和跟随跟踪是不可能的。
另一个方面,只有将收集到的数据进行汇总,许多性能问题才会变得清晰。 这样,你就可以知道在某个时间段内,方法调用相对于整个活动的重要性。 如果是单一的跟踪,你对你所看的数据的相对重要性没有概念。
这就是为什么JProfiler建立了一个所有观察到的调用堆栈的累积树,并注解有观察到的时间和调用次数。 时间顺序信息被消除,只保留总数。树中的每个节点代表一个至少被观察过一次的调用堆栈。 节点的子节点代表在该调用堆栈中看到的所有传出调用。
调用树是"CPU视图"部分的第一个视图,当你开始进行CPU分析时,它是一个很好的起点, 因为遵循方法调用从起点到最细化的终点的自上而下视图,最容易理解。 JProfiler按照子节点的总时间进行排序,所以你可以深度优先打开树,分析对性能影响最大的部分。
虽然所有的测量都是针对方法进行的,但JProfiler允许你通过在类或包级别上聚合调用树来获得更广阔的视角。 聚合级别选择器还包含一个"JEE/Spring组件"模式。如果你的应用程序使用JEE或Spring, 你可以使用该模式只查看类级别上的JEE和Spring组件。像URL这样的拆分节点会在所有聚合级别中保留。
如果所有类的方法都显示在调用树中,那么这棵树通常太深而无法管理。 如果你的应用程序被框架调用, 调用树的顶部将由你不关心的框架类组成,而你自己的类将被深埋。对库的调用会显示它们的内部结构, 可能会有上百层级的方法调用,而这些方法你并不熟悉,也无法影响。
这个问题的解决方案是对调用树应用过滤器,这样只会记录一些类。 作为一个积极的副作用,需要收集的数据更少,需要检测的类也更少,因此开销也就减少了。
默认情况下,分析会话配置了一个常用框架和库的排除包列表。
当然这个列表是不完整的,所以你最好删除它,定义自己感兴趣的包。 事实上,instrumentation和默认过滤器的组合非常不可取, 所以JProfiler建议在会话启动对话框中更改。
过滤器表达式与完全限定类名进行比对,所以com.mycorp.
,匹配所有嵌套包中的类, 比如com.mycorp.myapp.Application
。 有三种类型的过滤器,分别称为"profiled(被分析)"、"compact(压缩)"和"ignored(被忽略)"。 所有"profiled(被分析)"类中的方法都会被测量。这是你自己的代码所需要的。
在一个被"compact(压缩)"过滤器所包含的类中,只测量对该类的第一次调用,但不显示进一步的内部调用。 "compact(压缩)"是你想要针对库使用的,包括JRE。例如,当调用hashMap.put(a, b)
时, 你可能希望在调用树中看到HashMap.put()
,但仅限于此-它的内部工作原理应该被视为不透明的, 除非你是map实现的开发者。
最后,"ignored(被忽略)"的方法根本不会被分析。出于开销的考虑,可能不需要它们, 或者它们可能只是在调用树中分散注意力,比如在动态调用之间插入的内部Groovy方法。
手动输入包容易出错,所以你可以使用包浏览器。在开始会话之前,包浏览器只能向你显示配置的类路径中的包, 这往往不能覆盖所有实际加载的类。在运行时,包浏览器将向你显示所有加载的类。
配置的过滤器列表从上到下对每个类别进行评估。在每个阶段,如果有匹配,当前的过滤器类型可能会改变。 过滤器列表的开始是什么过滤器开始很重要。如果你以"profiled(被分析)"过滤器开始, 一个类的初始过滤器类型是"compact(压缩)",这意味着只有明确匹配才会被分析。
如果你"compact(压缩)"过滤器开始,类的初始过滤器类型是"profiled(被分析)"。 在这种情况下,除了明确排除的类之外,所有的类都会被分析。
要正确解读调用树,了解调用树节点上显示的数字很重要。任何节点都有两个时间值得关注,总时间和自身时间, 自身时间是节点的总时间减去嵌套节点中的总时间。
通常情况下,自身时间是很小的,除了compact(压缩)过滤类。大多数情况下,一个compact(压缩)过滤的类是一个叶子节点, 由于没有子节点,所以总时间等于自身时间。有时,一个compact(压缩)过滤的类会调用一个profiled(被分析)类, 例如通过回调或者因为它是调用树的入口点,比如当前线程的run
方法。 在这种情况下,一些未分析的方法消耗了时间,但没有显示在调用树中。这些时间会冒泡到调用树中第一个可用的祖先节点,并贡献给compact(压缩)过滤类的自身时间。
调用树中的百分比条显示的是总时间,但自身时间部分用不同的颜色显示。 除非同一层级上的两个方法重载,否则不显示方法的签名。在视图设置对话框中,有多种方法可以自定义调用树节点的显示。 例如,你可能希望将自身时间或平均时间显示为文本,始终显示方法签名或更改使用的时间刻度。 此外,百分比计算也可以基于父节点时间而不是整个调用树的时间。
在调用树的顶部,有几个视图参数可以改变显示的分析数据的类型和范围。默认情况下,会累积所有线程。 JProfiler以每个线程为基础维护CPU数据,你可以显示单个线程或线程组。
在任何时候,每个线程都有一个相应的线程状态。如果线程已经准备好处理字节码指令,或者当前正在CPU处理器上执行这些指令, 则线程状态称为"就绪(Runnable)"。 在寻找性能瓶颈时,该线程状态是值得关注的,所以默认选择它。
另外,一个线程可能正在等待一个Monitor,例如通过调用Object.wait()
或Thread.sleep()
, 在这种情况下,该线程状态称为"等待(Waiting)"。一个线程在试图获取Monitor时被阻塞, 例如在synchronized
代码块的边界处,则处于"阻塞(Blocking)"状态。
最后,JProfiler增加了一个合成的"Net I/O"状态,用于跟踪线程等待网络数据的时间。 这对于分析服务器和数据库驱动程序很重要,因为该时间可能与性能分析有关,例如调查缓慢的SQL查询。
如果你对挂钟时间感兴趣,你必须选择线程状态"所有状态",同时选择一个线程。 只有这样,你才能将时间与你在代码中调用System.currentTimeMillis()
计算出的持续时间进行比较。
如果你想将选定的方法转移到不同的线程状态,你可以通过一个方法触发器和"覆盖线程状态"触发动作来实现, 或者通过使用嵌入式 或注入式探针API中的 ThreadStatus
类来实现。
有两种方式可以在调用树中搜索文本。首先,通过调用菜单 视图->查找会激活一个快速搜索选项, 或直接开始在调用树中输入。按PageDown
键后,匹配项将被高亮显示,并提供搜索选项。 通过上箭头
和下箭头
键,你可以循环查看不同的匹配项。
搜索方法、类或包的另一种方法是使用调用树底部的视图过滤器。在这里你可以输入一个以逗号分隔的过滤表达式列表。 以"-"开头的过滤器表达式类似于被忽略(Ignored)过滤器。以"!"开头的表达类似于压缩(Compact)过滤器。 所有其他的表达式类似于被分析(Profiled)过滤器。就像过滤器设置一样,初始过滤器类型决定了默认情况下是包含还是排除类。
单击视图设置文本字段左侧的图标,会显示视图过滤器选项。默认情况下,匹配模式为"包含",但搜索特定包时, "头部匹配"可能更合适。
另一种查看调用树的方式是火焰图。你可以通过调用相应的调用树分析 将整个或部分调用树显示为火焰图。