剖析 Java 应用的性能

本页面介绍如何修改 Java 应用以捕获性能剖析数据,并将该数据发送到您的 Google Cloud 项目。如需了解性能剖析的常规信息,请参阅性能剖析相关概念

Java 的性能剖析文件类型:

  • CPU 时间
  • 堆(需要 Java 11 或 App Engine 标准环境,默认处于停用状态)
  • 实际用时(不适用于 Java 8 App Engine 标准环境)

支持的 Java 语言版本:

  • 基于热点的 JVM(包括 Oracle JDK 和一些 OpenJDK Build),适用于 Java 8、11 或更高版本。

支持的性能剖析代理版本:

  • 支持代理的最新版本。通常,不支持发布超过一年的版本。我们建议您使用最近发布的代理版本。

支持的操作系统:

  • Linux。只有使用 glibcmusl 实现其标准 C 库的Linux 内核支持剖析 Java 应用的性能。如需了解针对 Linux Alpine 内核的特定配置信息,请参阅在 Linux Alpine 上运行

支持的环境:

启用 Profiler API

在使用性能剖析代理之前,请确保已启用底层 Profiler API。您可以使用 Google Cloud CLI 或 Google Cloud 控制台查看该 API 的状态以及在必要时启用该 API:

gcloud CLI

  1. 如果您尚未在工作站上安装 Google Cloud CLI,请参阅 Google Cloud CLI 文档

  2. 运行以下命令:

    gcloud services enable cloudprofiler.googleapis.com
    

如需了解详情,请参阅 gcloud services

Google Cloud 控制台

  1. Enable the required API.

    Enable the API

  2. 如果系统显示 API 已启用,则表示此 API 已经启用。如未显示,请点击启用按钮。

向服务账号授予 IAM 角色

如果您在 Google Cloud 资源上部署应用,并且使用的是默认服务账号,并且未修改向该服务账号授予的角色,则可以跳过本部分。

如果您执行以下任何操作,则需要向服务账号授予 Cloud Profiler Agent (roles/cloudprofiler.agent) IAM 角色:

  1. 您使用的是默认服务账号,但修改了其角色授予。
  2. 您使用的是用户创建的服务账号。
  3. 您使用的是 Workload Identity,请向 Kubernetes 服务账号授予 Cloud Profiler Agent 角色。

您可以使用 Google Cloud 控制台或 Google Cloud CLI 向服务账号授予 IAM 角色。例如,您可以使用 gcloud projects add-iam-policy-binding 命令:

gcloud projects add-iam-policy-binding GCP_PROJECT_ID \
    --member serviceAccount:MY_SVC_ACCT_ID@GCP_PROJECT_ID.iam.gserviceaccount.com \
    --role roles/cloudprofiler.agent

在使用上一个命令之前,请替换以下内容:

  • GCP_PROJECT_ID:您的项目 ID。
  • MY_SVC_ACCT_ID:您的服务账号的名称。

如需了解详情,请参阅管理对项目、文件夹和组织的访问权限

安装 Profiler 代理

Compute Engine

  1. 为 Profiler 代理创建安装目录,例如 /opt/cprof

     sudo mkdir -p /opt/cprof

  2. storage.googleapis.com 代码库下载代理归档文件,然后将其解压缩至安装目录:

    wget -q -O- https://storage.googleapis.com/cloud-profiler/java/latest/profiler_java_agent.tar.gz \
    | sudo tar xzv -C /opt/cprof

GKE

修改 Dockerfile 以便为 Profiler 代理创建安装目录,下载代理归档文件并将其解压缩到安装目录中。

Linux(基于 glibc 的 C 库)

使用以下安装命令:

RUN mkdir -p /opt/cprof && \
  wget -q -O- https://storage.googleapis.com/cloud-profiler/java/latest/profiler_java_agent.tar.gz \
  | tar xzv -C /opt/cprof

Linux Alpine(基于 musl 的 C 库)

使用以下安装命令:

wget -q -O- https://storage.googleapis.com/cloud-profiler/java/latest/profiler_java_agent_alpine.tar.gz \
| tar xzv -C /opt/cprof

柔性环境

如果您使用的是 Google Java 8 运行时基础映像或 Java 9 / Jetty 9 运行时基础映像,那么系统已预装 Profiler 代理,因此您无需执行其他步骤来安装该代理。

对于所有其他基础映像,您需要安装该代理。例如,以下 Dockerfile 包含使用 openjdk:11-slim 映像安装 Profiler 代理的说明,它定义了启动应用时要使用的默认参数:

FROM openjdk:11-slim

COPY . .
RUN  apt-get update \
     && apt-get install wget \
     && rm -rf /var/lib/apt/lists/*

RUN mkdir -p /opt/cprof && \
    wget -q -O- https://storage.googleapis.com/cloud-profiler/java/latest/profiler_java_agent.tar.gz \
    | tar xzv -C /opt/cprof

CMD ["java", "-agentpath:/opt/cprof/profiler_java_agent.so=OPTION1,OPTION2", "-jar", "PATH_TO_YOUR_JAR_FILE"]

如需将此 Dockerfile 与 App Engine 柔性环境一起使用,您需要执行以下操作:

  • OPTION1OPTION2 替换为应用所需的代理配置值,并将 PATH_TO_YOUR_JAR_FILE 替换为 jar 文件的路径。
  • Dockerfile 放在 app.yaml 文件所在的目录中。
  • 修改 app.yaml 文件以指定自定义运行时。如需了解详情,请参阅构建自定义运行时

标准环境

如果您使用的是 Java 运行时环境,那么系统已预装 Profiler 代理,因此您无需执行其他步骤来安装该代理。对于 Java 11 及更高版本,该库已预安装在 /opt/cprof 中。

Google Cloud 外部

  1. 为 Profiler 代理创建安装目录,例如 /opt/cprof

     sudo mkdir -p /opt/cprof

  2. storage.googleapis.com 代码库下载代理归档文件,然后将其解压缩至安装目录:

    wget -q -O- https://storage.googleapis.com/cloud-profiler/java/latest/profiler_java_agent.tar.gz \
    | sudo tar xzv -C /opt/cprof

如需列出所有可供下载的代理版本,请运行以下命令:

gcloud storage ls gs://cloud-profiler/java/cloud-profiler-*

命令响应如下所示:

gs://cloud-profiler/java/cloud-profiler-java-agent_20191014_RC00.tar.gz
gs://cloud-profiler/java/cloud-profiler-java-agent_20191021_RC00.tar.gz
gs://cloud-profiler/java/cloud-profiler-java-agent_20191028_RC00.tar.gz

如需下载特定版本的代理,请将其网址传递给下载命令。例如,如需下载 2019 年 10 月 28 日构建的代理,您可以使用以下语句:

wget -q -O- https://storage.googleapis.com/cloud-profiler/java/cloud-profiler-java-agent_20191028_RC00.tar.gz \
  | sudo tar xzv -C /opt/cprof

在初始化代理期间会记录代理的版本。

加载 Profiler 代理

如需剖析应用的性能,请像往常运行程序一样启动 Java,但要指定代理配置选项。请指定指向代理库的路径,然后您可以将选项传递到该库。

对于 App Engine 标准环境,系统会自动加载和配置该代理。如需详细了解如何配置和启动您的程序,请直接跳到启动程序

代理配置

如需配置性能剖析代理,请在启动应用时包含 -agentpath 标志:

 -agentpath:INSTALL_DIR/profiler_java_agent.so=OPTION1,OPTION2,OPTION3

在此表达式中,INSTALL_DIR 是性能剖析代理的路径,而 OPTION1OPTION2OPTION3 是代理配置选项。例如,如果在前一个表达式中将 OPTION1 替换为 --cprof_service=myapp,那么您可以将服务名称设置为 myapp。对于选项数量或其排序方式,没有任何限制。下表列出了支持的配置选项:

代理选项 说明
-cprof_service 如果应用不在 App Engine 中运行,您必须使用此配置选项来设置服务名称。 如需了解服务名称限制,请参阅服务名称和版本参数
-cprof_service_version 如果希望能够使用 Profiler 界面按服务版本分析性能剖析数据,请使用此选项设置版本。如需了解版本限制,请参阅服务名称和版本参数
-cprof_project_id 如果应用不在 App Engine 中运行,您必须使用此配置选项来设置服务名称。 如需了解详情,请参阅剖析在 Google Cloud 外部运行的应用的性能
-cprof_zone_name 应用在 Google Cloud 中运行时,性能剖析代理通过与 Compute Engine 元数据服务通信来确定相应区域。如果性能剖析代理无法与该元数据服务通信,您需要使用此选项。
-cprof_gce_metadata_server_retry_count
-cprof_gce_metadata_server_retry_sleep_sec
这两个选项共同定义 Profiler 代理在与 Compute Engine 元数据服务通信时使用的重试政策, 以收集您的 Google Cloud 项目 ID 和区域信息。

默认政策是最多尝试 3 次,每两次尝试之间会等待 1 秒。此政策足够满足大多数配置的需要。
-cprof_cpu_use_per_thread_timers 如需获得最准确的 CPU 时间性能剖析文件,请将此选项设置为 true。使用此选项会导致每线程开销增加。

默认值为 false。
-cprof_force_debug_non_safepoints 默认情况下,性能剖析代理会强制 JVM 为所有即时 (JIT) 生成的代码生成调试信息,并生成所有安全点的调试信息。这样会使 CPU 时间和堆性能剖析文件的函数和行级位置信息最准确,但会产生额外的代理开销。您可以通过将此选项设置为 false 来禁止生成 JIT 代码的调试信息。

默认值为 true。
-cprof_wall_num_threads_cutoff 默认情况下,如果应用中的线程总数超过 4096,则系统不会收集实际用时性能剖析文件。该限制可确保在收集性能剖析文件时,遍历该线程栈的费用降至最低。 如果您的服务运行的线程通常会超出 4096 个,并且您愿意以额外开销为代价收集性能剖析数据,请使用此标志来提升限制。

默认限制为 4096 个线程。
-cprof_enable_heap_sampling 如需为 Java 11 及更高版本启用堆性能剖析,请设置
-cprof_enable_heap_sampling=true。Java 10 及更低版本不支持堆性能剖析。

堆性能剖析默认处于停用状态。

启用堆性能剖析时,默认情况下,采样间隔设置为 512 KiB。对于大多数应用而言,此间隔已足够,并且会产生低于 0.5% 的应用开销。支持从 256 KiB (262144) 到 1024 KiB (1048576) 的采样间隔。 例如,如需将采样间隔设置为 256 KiB(这样会使采样率加倍),请添加以下代理选项:
-cprof_heap_sampling_interval=262144
同样,如需将采样间隔设置为 1024 KiB(这样会使采样率减半),请添加以下代理选项:
-cprof_heap_sampling_interval=1048576
如果您启用此性能剖析类型,请在部署应用时指定新的服务版本。如需了解详情,请参阅为什么我没有特定性能剖析文件类型的数据?

服务名称和版本参数

加载 Profiler 代理时,请指定服务名称参数和可选的服务版本参数,以对其进行配置。

凭借服务名称,Profiler 可以收集该服务所有副本的性能剖析数据。对于每个组合服务版本和区域中的每个服务名称,Profiler 服务将保证平均每分钟生成一个性能剖析文件的收集速率。

例如,如果您的服务有两个版本在三个区域中的副本上运行,那么 Profiler 平均每分钟将为该服务创建 6 个性能剖析文件。

如果您为副本使用不同的服务名称,那么对服务执行性能剖析的频率将超出必要频率,相应的开销也会更高。

选择服务名称时,应注意以下几点:

  • 选择的名称应能清楚地表示应用架构中的服务。如果您只运行单个服务或应用,那么服务名称的选择没那么重要;但如果您的应用作为一组微服务运行,则服务名称的选择较为重要。

  • 切勿在服务名称字符串中使用任何进程专用的值,例如进程 ID。

  • 服务名称字符串必须与如下正则表达式相符:

    ^[a-z0-9]([-a-z0-9_.]{0,253}[a-z0-9])?$

建议使用 imageproc-service 这样的静态字符串作为服务名称。

服务版本是可选的。如果您指定服务版本,则 Profiler 可以汇总来自多个实例的性能剖析信息并将其正确显示出来。服务版本可用于在部署服务时标记不同的版本。Profiler 界面支持您按服务版本过滤数据,这样一来,您就可以比较新旧版本代码的性能。

服务版本参数的值是一个使用自由格式的字符串,但此参数的值通常看起来像版本号,例如 1.0.02.1.2

启动程序

Compute Engine

像往常运行程序一样启动 Java,然后添加代理配置选项:

java \
    -agentpath:/opt/cprof/profiler_java_agent.so=-cprof_service=myapp,-cprof_service_version=1.0.0 \
    JAVA_OPTIONS -jar PATH_TO_YOUR_JAR_FILE PROGRAM_OPTIONS

GKE

修改服务容器 Dockerfile,以像往常运行程序一样启动 Java,然后添加代理配置选项:

CMD ["java", \
    "-agentpath:/opt/cprof/profiler_java_agent.so=-cprof_service=myapp,-cprof_service_version=1.0.0", \
     "-jar", "PATH_TO_YOUR_JAR_FILE" ]
    

柔性环境

修改 app.yaml 配置文件以设置 PROFILER_ENABLE 环境变量。然后像往常一样启动程序:

env_variables:
   PROFILER_ENABLE: true

如需了解详情,请参阅定义环境变量

标准环境

Java 21 运行时环境

如果您不使用旧版捆绑服务,请使用以下任一方法修改 app.yaml 文件以指定 agentpath 标志,从而启用性能分析器收集功能:

  • 设置 JAVA_TOOL_OPTIONS 环境变量:

    runtime: java21
    env_variables:
      JAVA_TOOL_OPTIONS: "-agentpath:/opt/cprof/profiler_java_agent.so=-logtostderr,-cprof_enable_heap_sampling=true"
    
  • 使用 entrypoint 元素指定 agentpath

    runtime: java21
    entrypoint: java \
      -agentpath:/opt/cprof/profiler_java_agent.so=-logtostderr,-cprof_enable_heap_sampling=true \
      Main.java
    

如果您使用的是旧版捆绑服务,请使用以下任一方法修改 appengine-web.xml 文件以指定 agentpath 标志,从而启用性能分析器收集功能:

  • 设置 JAVA_USER_OPTS 环境变量:

    <?xml version="1.0" encoding="utf-8"?>
    <appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
    <env-variables>
    <env-var name="JAVA_USER_OPTS" value="-agentpath:/opt/cprof/profiler_java_agent.so=-logtostderr,-cprof_enable_heap_sampling=true" />
    </env-variables>
    </appengine-web-app>
  • 设置 CPROF_ENABLE 环境变量:

    <?xml version="1.0" encoding="utf-8"?>
    <appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
    <env-variables>
    <env-var name="CPROF_ENABLE" value="-agentpath:/opt/cprof/profiler_java_agent.so=-logtostderr,-cprof_enable_heap_sampling=true" />
    </env-variables>
    </appengine-web-app>
  • 使用 entrypoint 元素指定 agentpath

    <?xml version="1.0" encoding="utf-8"?>
    <appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
      <entrypoint>
       java
       -agentpath:/opt/cprof/profiler_java_agent.so=-logtostderr,-cprof_enable_heap_sampling=true
      </entrypoint>
    </appengine-web-app>

如果为收集配置了新的性能剖析文件类型,请务必在部署应用时指定新的服务版本。如需了解详情,请参阅为什么我没有特定性能剖析文件类型的数据?

代理日志记录

性能剖析代理可以报告 App Engine 柔性环境、Compute Engine 和 GKE 的日志记录信息。性能剖析代理支持以下日志记录级别:

  • 0:记录所有消息。默认日志记录级别。
  • 1:记录警告、错误和严重消息。
  • 2:记录错误和严重消息。
  • 3:仅记录严重消息并停止应用。

如需使用默认日志记录级别将日志写入标准错误,请将 -logtostderr 附加到 -agentpath 配置。

如需将日志记录级别设置为仅记录错误和严重消息,请将 -minloglevel=2 附加到 -agentpath 配置。

例如,如需使错误消息和严重消息的日志记录能够写入标准错误,请将 -logtostderr‑minloglevel=2 附加到 -agentpath 配置:

 java -agentpath:/opt/cprof/profiler_java_agent.so=-cprof_service=myapp,-logtostderr,-minloglevel=2 \
   -jar myApp.jar

问题排查

本部分列出了剖析 Java 应用性所特有的问题。有关常见问题的帮助,请参阅问题排查

行为 原因 解决方案
您启用了多个堆分析器,但没有性能剖析数据。 同时使用多个堆分析器会停用对 Java 的所有堆性能剖析支持。这是 JVM 的局限性。 启用 1 个分析器。

使用 Linux Alpine 运行

只有 Google Kubernetes Engine 配置支持适用于 Linux Alpine 的 Java 性能剖析代理。

如需安装适用于 Linux Alpine 的最新 Java 性能分析代理,请参阅安装 Profiler 代理

身份验证错误

如果您的 Docker 映像使用 Linux Alpine(如 golang:alpine 或者只是 alpine)运行,您可能会看到以下身份验证错误:

connection error: desc = "transport: authentication handshake failed: x509: failed to load system roots and no roots provided"

请注意,您必须启用代理日志记录才能看到该错误。

该错误表示使用 Linux Alpine 的 Docker 映像没有默认安装 SSL 根证书。这些证书为性能剖析代理与性能剖析器 API 进行通信所必需。如需解决此错误,请将以下 apk 命令添加到您的 Dockerfile 中:

FROM alpine
...
RUN apk add --no-cache ca-certificates

然后,您需要重新构建并重新部署应用。

后续步骤