剖析 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. 在 Google Cloud 控制台的导航面板中,选择 API 和服务,点击启用 API 和服务,然后启用 Cloud Profiler API:

    前往 Profiler API 设置

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

安装 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

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

gsutil 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

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

后续步骤