コンテナイメージの最適化
コンテナイメージを最適化することで、ロード時間と起動時間を短縮することができます。イメージの最適化は次の方法で行うことができます。
コンテナイメージのサイズを可能な限り小さくする
ネストされたjarパッケージの使用を避ける
TEM Jar/War方式を使用してデプロイする
TEM Jar/War方式でのデプロイはデフォルトで提供される、jarパッケージイメージビルドのベストプラクティスです。TEMはビルドキャッシュを有効に利用できるビルドフローをデフォルトで提供し、新世代のビルドツールであるBuildkitを使用して高速でビルドを行います。ビルド速度を50%以上最適化するとともに、ビルドフロー全体を追跡可能にし、ビルドログの確認も可能となり、簡単で効率的です。
アプリケーションアクセラレーションの設定
TEM Jar/War方式を使用してデプロイし、KONA JDK 11/Open JDK 11実行環境を選択すると、TEMはデフォルトでアプリケーションアクセラレーション機能を有効化します。またSpringBootをデフォルトでサポートし、アプリケーションを変更せずにアクセラレーションできます。TEMはOpen JDKのAppCDS特性を強化しており、既存のSpringBootネストJarパッケージの構造を変更する必要がありません。TEMはJavaのアプリケーションアクセラレーションのベストプラクティスを直接提供することで、インスタンス拡張時の起動時間を10%~40%短縮します。
JVMパラメータの最適化
コンテナメモリリソースを感知可能なJDKの使用
仮想マシンと物理マシンにおいて、JVMはCPUとメモリの割り当てについて、一般的な場所(例えば、Linuxの/proc/cpuinfoおよび/proc/meminfoなど)から使用可能なCPUとメモリを検索します。しかし、コンテナが実行中の場合、CPUおよびメモリの制限条件は/proc/cgroups/...に保存されています。旧バージョンのJDKは引き続き/proc(/proc/cgroupsではありません)内を検索しますが、これによってCPUとメモリの使用量が割り当ての上限を超過し、様々な深刻な問題を引き起こす可能性がありました。
スレッドが多すぎる。スレッドプールのサイズがRuntime.availableProcessors()によって設定されるため
JVMのメモリ使用がコンテナメモリの上限を超過し、コンテナのOOMKilledを引き起こす。
JDK 8u131はまず、UseCGroupMemoryLimitForHeapのパラメータを実装しました。ただしこのパラメータには欠陥がありました。アプリケーションにUnlockExperimentalVMOptionsおよびUseCGroupMemoryLimitForHeapパラメータを追加すると、JVMは確かにコンテナメモリを感知し、アプリケーションの実際のヒープサイズを制御できますが、コンテナに割り当てられたメモリを十分に利用することはできませんでした。
このため、JVMは、ヒープサイズのより適切な計算を支援するための-XX:MaxRAMFractionフラグを提供しました。MaxRAMFractionのデフォルト値は4(すなわち4で割る)ですが、これは分数であり、パーセンテージではないため、有効利用可能な可用メモリの値を設定することは困難でした。
JDK 10には、コンテナ環境に対する良好なサポートが付帯しています。LinuxコンテナでJavaアプリケーションを実行する場合、JVMはUseContainerSupportオプションを使用してメモリ制限を自動検出します。その後、InitialRAMPercentage、MaxRAMPercentage、MinRAMPercentageを使用してメモリの制御を行います。このとき使用されるのは分数ではなくパーセンテージであり、より正確になっています。
デフォルトでは、UseContainerSupportパラメータはアクティブになっており、MaxRAMPercentageは25%、MinRAMPercentageは50%です。
このMinRAMPercentageはヒープサイズの最小値を設定するために用いるものではなく、物理サーバー(またはコンテナ)内の総使用可能メモリが250MB未満になった場合に、JVMがこのパラメータを使用してヒープサイズを制限するものだという点に注意が必要です。
同じように、MaxRAMPercentageは物理サーバー(またはコンテナ)内の総使用可能メモリが250MBを超えた場合に、JVMがこのパラメータを使用してヒープサイズを制限するものです。
このいくつかのパラメータはすでにJDK 8u191に移植されています。UseContainerSupportはデフォルトでアクティベートされています。-XX:InitialRAMPercentage=50.0 -XX:MaxRAMPercentage=80.0を設定することで、JVMがコンテナの可用メモリを感知して有効利用することが可能になります。-Xms -Xmxを指定した場合、InitialRAMPercentageとMaxRAMPercentageは無効になる点に注意が必要です。
最適化コンパイラの無効化
JVMでは、デフォルトで複数の段階でのJITコンパイルが行われます。これらの段階はアプリケーションの効率を向上させますが、メモリ使用のオーバーヘッドを増加させ、起動時間も長くなります。
短時間で実行するクラウドネイティブアプリケーションの場合は、次のパラメータを使用して最適化段階を無効化し、長時間の実行効率を犠牲にしてより短い起動時間のほうを取ることを検討できます。
JAVA_TOOL_OPTIONS="-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
クラス検証の無効化
JVMはクラスをメモリ内にロードして実行する際、そのクラスが改ざんされたり悪意ある変更または損傷に遭ったりしていないかを検証します。しかし、クラウドネイティブな環境では、CI/CDパイプラインも通常はクラウドネイティブプラットフォームから提供されるため、アプリケーションのコンパイルとデプロイは信頼できると言えます。このため、次のパラメータを使用して検証を無効化することを検討すべきです。起動時に大量のクラスをロードしている場合、検証を無効化することで起動速度が向上する可能性があります。
JAVA_TOOL_OPTIONS="-noverify"
スレッドスタックサイズの縮小
大多数のJava Webアプリケーションは、それぞれが1つのスレッドに接続するモジュールをベースにしています。それぞれのJavaスレッドはマシンメモリ(ヒープメモリではありません)を消費します。これはスレッドスタックと呼ばれ、各スレッドはデフォルトで1MBです。アプリケーションが100の同時実行リクエストを処理する場合、少なくとも100のスレッドが存在する可能性があり、これは100MBのスレッドスタックスペースの使用に相当します。このメモリはヒープサイズの計算には含まれません。次のパラメータを使用することで、スレッドスタックのサイズを縮小することができます。
JAVA_TOOL_OPTIONS="-Xss256k"
サイズを小さくしすぎると、java.lang.StackOverflowErrorが発生しますので注意が必要です。アプリケーションの分析を行い、設定すべき最適なスレッドスタックサイズを見つけることができます。
Spring bootアプリケーションの最適化
Spring Boot 2.2またはそれ以上のバージョンを使用
Spring Bootはバージョン2.2から、起動速度の大幅な最適化を行いました。ご利用のSpring Bootがバージョン2.2より低い場合は、アップグレードまたは手動での最適化をご検討ください。 遅延初期化の使用
Spring Boot 2.2およびそれ以降のバージョンでは、グローバル遅延初期化を有効にして起動速度を上昇されることができます。ただし、その代わりに最初のリクエストの遅延時間が長くなる場合があります。コンポーネントの初回初期化を待つ必要があるためです。
application.propertiesで遅延初期化を有効化することができます。
spring.main.lazy-initialization=true
もしくは、次の環境変数を使用します。
SPRING_MAIN_LAZY_INITIALIZATIION=true