Docker 環境での Spring Boot アプリケーションのメモリ急騰の分析と解決策

関連タグ:1.FAQ about errors of docker login, docker push, and docker pull
2.How to Install Docker on Ubuntu 20

Spring Boot の概要

シンプルな Spring Boot アプリケーションで、ユーザーがほぼ 1 人しかいないにもかかわらず、メモリ使用量が 1.2 GB に達しており、深刻な問題となっています。

Spring Boot アプリケーションのメモリ急騰


シンプルな Spring Boot アプリケーションで、ユーザーがほぼ 1 人しかいないにもかかわらず、メモリ使用量が 1.2 GB に達しており、深刻な問題となっています。
サービスステータス

サービス数が少なくサーバーリソースに余裕があったため、多くのサービスが JVM パラメータを設定せずに起動されていました(レガシーな問題)。その結果、各サービスの起動時に 1.2 GB〜2 GB のメモリを占有し、中にはそこまで必要ないサービスもあります。では、JVM メモリパラメータを設定しない場合、Spring Boot では JVM メモリはどのように構成されるのでしょうか。

Spring Boot の JVM デフォルトメモリ構成


Spring Boot プロジェクトを実行する際、JVM メモリパラメータを設定しない場合、Spring Boot はデフォルトで JVM 自体のデフォルト構成戦略を使用します。リソースが十分な環境では、開発者はメモリ構成をあまり気にしません。しかし、リソース不足や JVM の最適化が必要になった場合は、JVM のデフォルトメモリ構成戦略を理解しておく必要があります。
JVM メモリに関する最も一般的な設定は、初期ヒープサイズ(-Xms)と最大ヒープメモリ(-Xmx)です。多くの開発者は明示的に設定せず、JVM のデフォルト値を使用しています。特に開発環境で多数のマイクロサービスを起動する場合、メモリ不足に陥りやすい問題があります。

Spring Boot の JVM デフォルトメモリ構成戦略は、大規模メモリ空間と小規模メモリ空間(192 MB 未満)の 2 つのシナリオに分かれます。
4 GB のメモリを例にとると、初期ヒープメモリサイズと最大ヒープメモリサイズは以下のようになります。

デフォルトでは、最大ヒープメモリは物理メモリの 1/4 を占有し、アプリケーションがこの制限を超えると OutOfMemoryError 例外がスローされます。初期ヒープメモリサイズは物理メモリの 1/64 です。
アプリケーションがモバイル端末上で実行される場合、または物理メモリが 192 MB 未満の場合、JVM のデフォルトの初期ヒープメモリサイズと最大ヒープメモリサイズは以下のようになります。

最大ヒープメモリは物理メモリの 1/2、初期ヒープメモリサイズは物理メモリの 1/64 ですが、初期ヒープメモリが 8 MB 以上の場合は 8 MB になります。

デフォルトでは、空きヒープメモリが 40% 未満になると、JVM は -Xmx の上限に達するまでヒープを拡大します。空きヒープメモリが 70% を超えると、JVM は -Xms の下限に達するまでヒープを縮小します。
そのため、一般的にサーバーでは -Xms と -Xmx を同じ値に設定し、各 GC 後のヒープリサイズを回避します。オブジェクトのヒープメモリは、ガベージコレクタと呼ばれる自動メモリ管理システムによって回収されます。

最大ヒープメモリは JVM が使用するメモリの上限であり、実際の実行プロセスでは必要な分だけ使用されます。デフォルトでは、若年世代に割り当てられる最大領域はヒープ全体の 1/3 です。

Spring Boot では、前述の問題に対応するため、各プログラムをデフォルト構成で起動すると、サーバー上に複数のアプリケーションをデプロイした際にメモリ不足が発生し、リソースの無駄が生じます。最もシンプルな対応は、java -jar で起動する際に適切な JVM メモリ構成パラメータを追加することです。
java -Xms64m -Xmx128m -jar xxx.jar
このプロジェクトでは Docker を使ったデプロイを採用しています。まず元の Dockerfile を確認してみましょう。

EXPOSE 8061

ADD target/certif-system-2.1.0.jar /certif-system-2.1.0.jar

ENTRYPOINT ["java", "-jar","/certif-system-2.1.0.jar"]

Spring Boot の最適化
JVM メモリの制限

Spring Boot は環境変数をアプリケーションに渡しますが、JAVA_OPTS はアプリケーションのためではなく、Java ランタイム自体のためのものです。そのため、$JAVA_OPTS 変数を exec java に渡す必要があります。これには Dockerfile に小さな変更を加える必要があります。
ENTRYPOINT exec java $JAVA_OPTS -jar app.jar
docker run コマンドの実行
これは、環境変数の JAVA_OPTS パラメーター情報が、実行時に -e で上書きされることを意味します。
docker run -e JAVA_OPTS='-Xmx1344M -Xms1344M -Xmn448M - XX:MaxMetaspaceSize =192M - XX:MetaspaceSize =192M'

java.security.egd の役割
SecureRandom は Java のさまざまなコンポーネントで広く使用されており、信頼性の高い乱数生成が可能です。ただし、大量の乱数を生成するシナリオではパフォーマンスが低下します。このような場合、「-Djava.security.egd=file:/dev/./urandom」を使用して乱数生成プロセスを高速化できます。
大量の乱数を使用する場合は、乱数ジェネレータとして /dev/./urandom を指定することを推奨します。
バグの原因については、以下のソースコードの 4 行目に注目してください。java.security.egd パラメータに file:/dev/random または file:/dev/urandom を指定すると、パラメータなしの NativeSeedGenerator コンストラクターが呼び出され、このパラメータなしのコンストラクターはデフォルトで file:/dev/random を使用します。OpenJDK のコードは HotSpot のコードとは異なり、OpenJDK は後の乱数生成時にこの変数を使用しません。

関連記事

特別オファーをもっと見る

  1. ショートメッセージサービス (SMS) & メールサービス

    5 万通のメールパッケージが USD 1.99 から、120 通のショートメッセージが USD 1.00 から

phone お問い合わせ
Hi, I'm Alibaba Cloud AI Assistant!
I can help with questions and solutions.