Java アプリケーションのプロファイリング

このページでは、プロファイリング データを収集し、そのデータを Google Cloud プロジェクトに送信するように Java アプリケーションを変更する方法について説明します。プロファイリングの全般的な情報については、プロファイリングのコンセプトをご覧ください。

Java のプロファイル タイプ:

  • CPU 時間
  • ヒープ(Java 11 または App Engine スタンダード環境が必要で、デフォルトでは無効)
  • 経過時間(Java 8 App Engine スタンダード環境では使用できません)

サポートされている Java 言語バージョン:

  • Java 8 または 11 以降用の HotSpot ベースの JVM(Oracle JDK と一部の OpenJDK ビルドを含む)。

サポートされているプロファイリング エージェントのバージョン:

  • エージェントの最新リリースがサポートされています。一般に、1 年を超える期間が経過したリリースはサポートされません。エージェントの最新リリース バージョンを使用することをおすすめします。

サポートされているオペレーティング システム:

  • Linux。Java アプリケーションのプロファイリングは、標準 C ライブラリが glibc または musl で実装されている Linux カーネルでサポートされています。Linux Alpine カーネルに固有の構成情報については、Linux Alpine で実行するをご覧ください。

サポートされる環境:

Profiler API を有効にする

プロファイリング エージェントを使用する前に、基盤となる Profiler API が有効になっている必要があります。Google Cloud CLI または Google Cloud Console のいずれかを使用して、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 はすでに有効になっています。そうでない場合は、[有効にする] ボタンをクリックします。

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 ファイルのパスに置き換えます。
  • Dockerfileapp.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

特定のバージョンのエージェントをダウンロードするには、その URL をダウンロード コマンドに渡します。たとえば、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 UI を使用してプロファイリング データを分析する機能が必要な場合は、このオプションを使用してバージョンを設定します。バージョンの制限については、サービス名とバージョンの引数をご覧ください。
-cprof_project_id Google Cloud の外部で実行している場合は、このオプションを使用して Google Cloud プロジェクト ID を指定します。詳細については、Google Cloud の外部で実行されているアプリケーションのプロファイリングをご覧ください。
-cprof_zone_name アプリケーションが Google Cloud で実行されている場合、プロファイリング エージェントは Compute Engine メタデータ サービスに接続してゾーンを決定します。プロファイリング エージェントがメタデータ サービスに接続できない場合は、このオプションを使用する必要があります。
-cprof_gce_metadata_server_retry_count
-cprof_gce_metadata_server_retry_sleep_sec
この 2 つのオプションにより、プロファイリング エージェントが Compute Engine メタデータ サービスに接続してGCP プロジェクト ID とゾーン情報を収集するときに使用する、再試行ポリシーが定義されます。

デフォルトのポリシーでは、1 秒間待機して再試行します。最大 3 回まで再試行します。大半の構成には、このポリシーで十分です。
-cprof_cpu_use_per_thread_timers 正確な CPU 時間をプロファイリングするには、このオプションを true に設定します。このオプションを使用すると、スレッドごとのオーバーヘッドが増加します。

デフォルト値は false です。
-cprof_force_debug_non_safepoints デフォルトで、プロファイリング エージェントは、すべてのセーフポイントのデバッグ情報だけでなく、すべてのジャストイン タイム(JIT)生成コードのデバッグ情報も JVM に生成させます。これにより、CPU 時間プロファイルとヒープ プロファイルで関数または行レベルの最も正確なロケーション情報を取得することはできますが、エージェントで追加のオーバーヘッドが発生します。このオプションを false に設定すると、JIT コードのデバッグ情報の生成を無効にできます。

デフォルト値は true です。
-cprof_wall_num_threads_cutoff デフォルトで、アプリケーションのスレッドの合計数が 4,096 を超える場合は、経過時間プロファイルが収集されません。この上限により、プロファイルの収集中に、スレッドのスタックを走査するコストを最小限に抑えることができます。サービスに通常 4,096 を超えるスレッドがあり、追加のオーバーヘッドが発生してもプロファイリング データを収集する場合は、このフラグを使用して上限を引き上げます。

デフォルトの上限は 4,096 スレッドです。
-cprof_enable_heap_sampling Java 11 以降でヒープ プロファイリングを有効にするには、
-cprof_enable_heap_sampling=true を設定します。Java 10 以前ではヒープ プロファイリングを使用できません。

ヒープ プロファイリングはデフォルトで無効になっています。

ヒープ プロファイリングを有効にすると、サンプリング間隔はデフォルトで 512 KiB に設定されます。ほとんどのアプリケーションは、この間隔で十分です。アプリケーションのオーバーヘッドは 0.5% 未満です。サンプリング間隔は 256 KiB(262144)~ 1,024 KiB(1048576)の範囲でサポートされています。たとえば、サンプリング間隔を 256 KiB に設定してサンプリング レートを 2 倍にするには、次のようにエージェント オプションを追加します。
-cprof_heap_sampling_interval=262144
同様に、サンプリング間隔を 1,024 KiB に設定して、サンプリング レートを半分にするには、次のようにエージェント オプションを追加します。
-cprof_heap_sampling_interval=1048576
このプロファイル タイプを有効にする場合は、アプリケーションをデプロイするときに新しいサービス バージョンを指定します。詳しくは、特定のプロファイル タイプのデータがないのはなぜですか?をご覧ください

サービス名とバージョンの引数

Profiler エージェントを読み込むときに、service-name 引数とオプションの service-version 引数を指定して構成します。

Profiler は、サービス名で指定されたサービスのすべてのレプリカからプロファイリング データを収集します。Profiler サービスは、サービス バージョンとゾーンの組み合わせに対して、1 つのサービス名について平均で 1 分あたり 1 個のプロファイルを作成します。

たとえば、1 つのサービスの 2 つのバージョンが 3 つのゾーンのレプリカで実行されている場合、Profiler はそのサービスについて 1 分あたり平均で 6 個のプロファイルを作成します。

レプリカで異なるサービス名を使用している場合、サービスが必要以上にプロファイリングされるため、オーバーヘッドが大きくなります。

サービス名を選択する場合は、次の点に注意してください。

  • アプリケーション アーキテクチャでサービスを明確に識別できる名前を選択してください。1 つのサービスまたはアプリケーションしか実行していない場合、どのようなサービス名を選択するかはさほど問題になりません。しかし、アプリケーションが一連のマイクロサービスとして実行されている場合は、名前の選択が重要になります。

  • service-name 文字列で、プロセス ID などのプロセス固有の値を使用しないでください。

  • service-name 文字列は次の正規表現と一致する必要があります。

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

サービス名として imageproc-service のような静的な文字列を使用することをおすすめします。

サービス バージョンは省略可能です。サービス バージョンを指定すると、複数のインスタンスからプロファイリング情報が集約され、バージョンごとに表示されます。これは、複数のバージョンがデプロイされているときの識別に役立ちます。Profiler UI では、データをサービス バージョンでフィルタできます。これにより、コードの新旧バージョンのパフォーマンスを比較できます。

service-version 引数には、自由形式の文字列を指定できますが、通常は、バージョン番号のような値を使用します(例: 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: 致命的問題のメッセージだけをログに記録してアプリケーションを停止します。

デフォルトのロギングレベルで標準エラーに記録できるようにするには、-agentpath 構成に -logtostderr を追加します。

エラー メッセージと致命的問題のメッセージだけを記録するようロギングレベルを設定するには、-agentpath 構成に -minloglevel=2 を追加します。

たとえば、エラー メッセージと致命的問題のメッセージを標準エラーに記録できるようにするには、-agentpath 構成に -logtostderr‑minloglevel=2 を追加します。

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

トラブルシューティング

このセクションでは、Java アプリケーションのプロファイリングに固有の問題について説明します。一般的な問題のヘルプについては、こちらのトラブルシューティングをご覧ください。

動作 原因 解決策
複数のヒープ プロファイラを有効にしているが、プロファイル データがありません。 複数のヒープ プロファイラを同時に使用すると、Java のすべてのヒープ プロファイリング サポートが無効になります。これは JVM の制限です。 1 つのプロファイラを有効にします。

Linux Alpine で実行する

Linux Alpine 用の Java プロファイリング エージェントは、Google Kubernetes Engine の構成でのみサポートされています。

Linux Alpine 用の最新の Java プロファイリング エージェントをインストールするには、Profiler エージェントをインストールするをご覧ください。

認証エラー

Linux Alpine で Docker イメージ(golang:alpinealpine など)を使用すると、次の認証エラーが表示されることがあります。

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

エラーの詳細を確認するには、エージェント ロギングを有効にする必要があります。

上記のエラーは、Linux Alpine の Docker イメージに、デフォルトでインストールされているはずのルート SSL 証明書がないことを示しています。これらの証明書は、プロファイリング エージェントが Profiler API と通信するために必要になります。このエラーを解決するには、次の apk コマンドを Dockerfile に追加します。

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

その後、アプリケーションを再度ビルドし、デプロイする必要があります。

次のステップ