Java は、活発なオープン ソース コミュニティと完全な環境上の利点のおかげで、過去 20 年間で最も人気のあるプログラミング言語の 1 つとなっています。クラウドネイティブ時代に入り、急成長するクラウドネイティブテクノロジーは、クラウドコンピューティングの利益を解き放ち、ビジネスのクラウドネイティブ変革を促進し、企業のデジタル変革を加速します。 しかし、Java のクラウド ネイティブ変換には大きな課題が伴います。 Java の動作メカニズムとクラウドネイティブの特性の間には多くの矛盾があります。企業はクラウドネイティブテクノロジーを使用して徹底的なコスト最適化を実施し、リソースコスト管理はかつてないレベルにまで引き上げられました。パブリック クラウド上のリソースは使用量に基づいて課金されるため、ユーザーはリソース使用量に非常に敏感です。メモリ使用量の点では、Java 仮想マシンに基づく実行メカニズムにより、どの Java プログラムにも固定の基本メモリ オーバーヘッドが発生します。 C++/Golangなどのネイティブ言語と比較すると、Javaアプリケーションは膨大な量のメモリを占有し、「メモリイーター」と呼ばれます。したがって、Java アプリケーションをクラウドに移行するにはコストがかかります。さらに、アプリケーションがクラウドに統合されると、システムの複雑さが増します。一般ユーザーは、クラウド上の Java アプリケーションのメモリを明確に理解しておらず、アプリケーションのメモリを適切に構成する方法を知りません。 OOM 問題が発生すると、トラブルシューティングが困難になり、多くの問題が発生します。 ヒープメモリが Xmx を超えていないのに OOM が発生するのはなぜですか?オペレーティング システムと JVM 間のメモリ関係を理解するにはどうすればよいでしょうか?プログラムが占有するメモリが Xmx を超えるのはなぜですか?メモリはどこで使用されますか?オンライン コンテナー内のプログラムにより多くのメモリが必要なのはなぜですか?この記事では、クラウドネイティブ Java アプリケーションの進化の実践において EDAS ユーザーが遭遇する問題を分析し、クラウドネイティブ Java アプリケーションのメモリ構成に関する推奨事項を示します。 1. 背景知識K8s アプリケーションのリソース構成クラウドネイティブ アーキテクチャは K8s に基づいています。アプリケーションは K8s にデプロイされ、コンテナ グループの形式で実行されます。 K8s リソース モデルには、リソース要求とリソース制限の 2 つの定義があります。 K8s は、コンテナに要求された数のリソースがあることを保証しますが、コンテナが制限数を超えるリソースを使用することを許可しません。次のメモリ構成を例にとると、コンテナは少なくとも 1024Mi のメモリ リソースを取得できますが、4096Mi を超えることはできません。メモリ使用量が制限を超えると、コンテナは OOM 状態になり、K8s コントローラによって再起動されます。 仕様: コンテナOOMコンテナの OOM メカニズムについては、まずコンテナの概念を確認する必要があります。コンテナについて話すとき、これはサンドボックス テクノロジであると言われます。サンドボックスとして、コンテナは内部で比較的独立しており、境界とサイズがあります。コンテナ内の独立した動作環境は、Linux 名前空間メカニズムを通じて実装されます。コンテナ内の PID、マウント、UTS、IPD、ネットワークなどの名前空間は隠されているため、ホストの名前空間や他のコンテナの名前空間はコンテナ内では見えません。いわゆるコンテナの境界とサイズは、コンテナによる CPU、メモリ、IO、その他のリソースの使用に対する制約を指します。そうしないと、単一のコンテナが多くのリソースを占有しすぎて、他のコンテナの実行速度が低下したり、異常な動作をしたりする可能性があります。 cgroup は、Linux カーネルによって提供されるメカニズムであり、単一のプロセスまたは複数のプロセスによって使用されるリソースを制限できます。これは、コンテナ リソース制約を実装するためのコア テクノロジーでもあります。コンテナは、オペレーティング システムの観点から見ると、単なる特別なプロセスにすぎません。プロセスのリソース使用量は、Cgroup の制約に従います。プロセスによって使用されるメモリの量が Cgroup 制限を超えると、システムの OOM Killer によって容赦なくプロセスが強制終了されます。 したがって、いわゆるコンテナ OOM は、実際には Linux システム上で実行されているコンテナ プロセスに OOM があることを意味します。 Cgroup は、あまり知られていないテクノロジーではありません。 Linux ではこれをファイル システムとして実装しており、これはすべてがファイルであるという Unix の哲学と一致しています。 Cgroup V1 の場合、コンテナ内の /sys/fs/cgroup/ ディレクトリで現在のコンテナの Cgroup 構成を直接表示できます。 コンテナ メモリの場合、memory.limit_in_bytes と memory.usage_in_bytes は、メモリ制御グループで最も重要な 2 つのパラメーターです。前者は現在のコンテナ プロセス グループで使用できるメモリの最大値を識別し、後者は現在のコンテナ プロセス グループで実際に使用されているメモリの合計です。一般的に、使用値が最大値に近いほど、OOM のリスクが高くなります。 # 現在のコンテナのメモリ制限 JVM の OOMOOM に関しては、Java 開発者は JVM OOM に精通しています。 JVM にオブジェクト用のスペースを割り当てるための十分なメモリがなく、ガベージ コレクターに再利用するスペースがない場合、java.lang.OutOfMemoryError がスローされます。 JVM 仕様によれば、プログラム カウンターを除く他のメモリ領域で OOM が発生する可能性があります。最も一般的な JVM OOM 状況は次のとおりです。
2. ヒープメモリが Xmx を超えていないのに OOM が発生するのはなぜですか?多くの人がこのシナリオに遭遇したことがあると思います。 K8s にデプロイされた Java アプリケーションは頻繁に再起動され、コンテナの終了ステータスは終了コード 137、理由: OOM Killed となります。すべての情報は明らかな OOM を示しています。ただし、JVM 監視データは、ヒープ メモリ使用量が最大ヒープ メモリ制限 Xmx を超えていないことを示しています。 OOM 自動ヒープダンプ パラメータが設定されると、OOM が発生してもダンプ ファイルは生成されません。 上記の背景知識によれば、コンテナ内の Java アプリケーションでは、JVM OOM とコンテナ OOM という 2 種類の OOM 例外が発生する可能性があります。 JVM OOM は、JVM メモリ領域のスペース不足によって発生するエラーです。 JVM は積極的にエラーをスローし、プロセスを終了します。データを観察すると、メモリ使用量が制限を超えていることがわかります。JVM は対応するエラー レコードを残します。コンテナの OOM はシステムの動作です。コンテナ プロセス グループ全体で使用されるメモリが Cgroup 制限を超え、システム OOM Killer によって強制終了されます。関連する記録はシステム ログと K8s イベントに残されます。 一般に、Java プログラムのメモリ使用量は、JVM と Cgroup の両方からの制限を受けます。 Java ヒープ メモリは Xmx パラメータによって制限されます。制限を超えると、JVM OOM が発生します。プロセス全体のメモリは、コンテナのメモリ制限値によって制限されます。制限を超えると、コンテナ OOM が発生します。 OOM を識別してトラブルシューティングし、必要に応じて構成を調整するには、観測データ、JVM エラー レコード、システム ログ、K8s イベントを組み合わせる必要があります。 3. オペレーティング システムと JVM 間のメモリ関係を理解するにはどうすればよいでしょうか?前述のように、Java コンテナの OOM は、実際には、Java プロセスによって使用されるメモリが Cgroup 制限を超え、オペレーティング システムの OOM Killer によって強制終了されることを意味します。では、オペレーティング システムの観点から、Java プロセスのメモリをどのように見ればよいのでしょうか?オペレーティング システムと JVM にはそれぞれ独自のメモリ モデルがあります。これら 2 つはどのようにマッピングされますか? Java プロセスにおける OOM の問題を調査するには、JVM とオペレーティング システム間のメモリ関係を理解することが重要です。 最も一般的に使用される OpenJDK を例にとると、JVM は基本的にオペレーティング システム上で実行される C++ プロセスであるため、そのメモリ モデルも Linux プロセスの一般的な特性を備えています。 Linux プロセスの仮想アドレス空間は、カーネル空間とユーザー空間に分かれています。ユーザー空間はさらに多くのセグメントに分割されます。ここでは、この記事に関連性の高いいくつかのセグメントを選択して、JVM メモリとプロセス メモリのマッピング関係について説明します。
前述のように、ヒープ スペースは Linux プロセス メモリ レイアウトと JVM メモリ レイアウトの両方に共通する概念です。それは最も混乱を招き、最も異なる概念です。 Java ヒープは Linux プロセス ヒープよりも小さくなります。これは、JVM によってプロセス ヒープ領域上に作成される論理領域です。プロセス ヒープ スペースには、Java スレッド スタック、コード キャッシュ、GC、コンパイラ データなど、JVM 仮想マシンの動作をサポートするメモリ データも含まれます。 4. プログラムが占有するメモリが Xmx を超えるのはなぜですか?メモリはどこで使用されますか?実は、誰もがよく知っているヒープ メモリに加えて、JVM には非ヒープ メモリと呼ばれるものもあります。 JVM によって管理されるメモリに加えて、JVM をバイパスせずに直接開かれるローカル メモリもあります。 Java プロセスのメモリ使用量は、次のように簡単にまとめることができます。 JDK8 では、JVM の内部メモリ使用量を追跡できるネイティブ メモリ トラッキング (NMT) 機能が導入されました。デフォルトでは、NMT は無効になっています。 JVM パラメータを使用して有効にします: -XX:NativeMemoryTracking=[off |概要 |詳細] $ java - Xms300m - Xmx300m - XX : + UseG1GC - XX : NativeMemoryTracking = summary - jar app .jar ここでは、最大ヒープメモリは 300M に制限され、GC アルゴリズムとして G1 が使用され、プロセスのメモリ使用量を追跡するために NMT が有効になっています。
NMT を有効にすると、jcmd コマンドを使用して JVM メモリ使用量を出力できます。ここではメモリの概要情報のみを表示でき、設定単位は MB です。 $ jcmd < pid > VM .native_memoryサマリー スケール= MB JVM 合計メモリネイティブメモリトラッキング: NMT レポートによると、プロセスには現在 1764 MB の予約済みメモリと 534 MB のコミット済みメモリがあり、これは最大ヒープ メモリの 300 MB を大幅に上回っています。予約とは、プロセスが使用できるメモリの量として理解できる、プロセスに対して連続した仮想アドレス メモリを開くことを指します。送信とは、仮想アドレスを物理メモリにマッピングすることを指し、これはプロセスによって現在占有されているメモリの量として理解できます。 NMT によってカウントされるメモリは、オペレーティング システムによってカウントされるメモリとは異なることに注意してください。 Linux はメモリを割り当てるときに遅延割り当てメカニズムに従います。プロセスが実際にアクセスした場合にのみ、メモリ ページを物理メモリにスワップします。したがって、top コマンドで表示されるプロセスの物理メモリ使用量は、NMT レポートで表示されるものとは異なります。ここでは、JVM の観点からメモリ使用量を示すために NMT のみが使用されます。 JavaヒープJava ヒープ(予約済み= 300 MB 、コミット済み= 300 MB ) ( mmap :予約済み= 300 MB 、コミット済み= 300 MB ) Java ヒープメモリはそのまま設定され、実際に 300M のメモリ空間が解放されます。 メタスペースクラス(予約済み= 1078 MB 、コミット済み= 61 MB ) (クラス #11183 ) ( malloc = 2 MB #19375 ) ( mmap :予約済み= 1076 MB 、コミット済み= 60 MB ) ロードされたクラスは Metaspace に保存され、11,183 個のクラスがロードされ、約 1G が保持され、61M が送信されます。 ロードするクラスが増えるほど、Metaspace の使用量も増えます。メタスペースのサイズは、-XX:MaxMetaspaceSize (デフォルトでは無制限) と -XX:CompressedClassSpaceSize (デフォルトでは 1G) によって制限されます。 糸スレッド(予約済み= 60 MB 、コミット済み= 60 MB ) (スレッド #61 ) (スタック:予約済み= 60 MB 、コミット済み= 60 MB ) JVM スレッド スタックにも一定量のスペースが必要です。ここでは、61 個のスレッドが 60M のスペースを占有し、各スレッドのデフォルトのスタック サイズは約 1M です。スタック サイズは -Xss パラメータによって制御されます。 コードキャッシュコード(予約済み= 250 MB 、コミット済み= 36 MB ) ( malloc = 6 MB #9546 ) ( mmap :予約済み= 244 MB 、コミット済み= 30 MB ) コード キャッシュは主に、JIT コンパイラとネイティブ メソッドによってコンパイルされたコードを格納するために使用されます。現在、36M のコードがキャッシュされています。コード キャッシュの容量は、-XX:ReservedCodeCacheSize パラメータを使用して設定できます。 GCGC (予約済み = 47MB、コミット済み = 47MB) GC ガベージ コレクターは、GC 操作をサポートするために、ある程度のメモリ領域も必要とします。 GC によって占有されるスペースは、選択された特定の GC アルゴリズムに関連しています。ここでの GC アルゴリズムは 47M を使用します。他の構成は同じまま、SerialGC に切り替えます。 GC (予約済み = 1MB、コミット済み = 1MB) SerialGC アルゴリズムは 1M のメモリのみを使用することがわかります。これは、SerialGC が単純なデータ構造と少量の計算データを含む単純なシリアル アルゴリズムであるため、メモリ使用量も少ないためです。ただし、単純な GC アルゴリズムはパフォーマンスの低下を招く可能性があるため、選択を行う前にパフォーマンスとメモリパフォーマンスのバランスを取る必要があります。 シンボルシンボル (予約済み = 15MB、コミット済み = 15MB) JVM のシンボルには、15M を占めるシンボル テーブルと文字列テーブルが含まれています。 非JVMメモリNMT は JVM 内のメモリのみをカウントでき、一部のメモリは JVM によって管理されません。 JVM 管理メモリに加えて、プログラムはオフヒープ メモリ ByteBuffer.allocateDirect を明示的に要求することもできます。これは -XX:MaxDirectMemorySize パラメータによって制限されます (デフォルトは -Xmx に等しい)。 System.loadLibrary によってロードされた JNI モジュールは、JVM 制御なしでオフヒープ メモリに適用することもできます。 要約すると、Java プロセスのメモリ使用量を正確に推定できるモデルは実際には存在しません。できるだけ多くの要素を考慮するしかありません。これらのメモリ領域の一部は、コード キャッシュやメタスペースなどの JVM パラメータによって容量が制限される場合がありますが、一部のメモリ領域は JVM によって制御されず、特定のアプリケーションのコードに関連しています。 合計メモリ = ヒープ + コードキャッシュ + メタスペース + スレッドスタック + 5. オンライン コンテナーではローカル テストよりも多くのメモリが必要なのはなぜですか?コンテナ対応のJVMバージョンを使用していない一般的な物理マシンまたは仮想マシンでは、-Xmx パラメータが設定されていない場合、JVM は一般的な場所 (Linux の /proc ディレクトリなど) から使用できる最大メモリ量を検索し、ホストの最大メモリの 1/4 をデフォルトの JVM 最大ヒープ メモリとして使用します。初期の JVM バージョンはコンテナに適合していませんでした。コンテナ内で実行する場合、JVM の最大ヒープはホスト メモリの 1/4 に設定されていました。一般的なクラスター ノードのホスト メモリは、ローカル開発マシンのホスト メモリよりもはるかに大きくなります。コンテナ内の Java プロセスのヒープ領域が大きいほど、消費するメモリも多くなります。同時に、コンテナは Cgroup リソース制限の対象となります。コンテナプロセスグループのメモリ使用量が Cgroup 制限を超えると、OOM になります。このため、OpenJDK 8u191 以降では、デフォルトで有効になっている UseContainerSupport パラメータが導入され、コンテナ内の JVM がコンテナのメモリ制限を認識し、Cgroup メモリ制限の 1/4 に応じて最大ヒープ メモリを設定できるようになりました。 オンラインサービスはより多くのメモリを消費する外部にサービスを提供するビジネスでは、新しいオブジェクトの作成や実行スレッドの開始など、よりアクティブなメモリ割り当てアクションが発生することがよくあります。これらの操作にはメモリ領域の割り当てが必要となるため、オンライン ビジネスでは多くのメモリが消費されることがよくあります。より多くのメモリを消費します。そのため、サービス品質を確保するためには、自社の業務トラフィックに応じてアプリケーションメモリ構成を適宜拡張する必要があります。 6. クラウドネイティブJavaアプリケーションのメモリ構成に関する推奨事項
|
<<: クラウドネイティブのビッグデータについて1つの記事で学ぶ
ユーザー エクスペリエンス デザインの専門家である Gil Remy 氏は最近、UX Magazin...
2010 年に設立され、1 万回も逃げていると言われている 123systems が、新しい XEN...
みなさんこんにちは、私は仙宇です今日はカフカについてお話しますメッセージが生成されるときに、パーティ...
多くの新規参入者の心の中では、SEO はオンライン プロモーションであり、オンライン プロモーション...
VMware (NYSE: VMW) は本日、拡大を続けるネットワークおよびセキュリティ ポートフォ...
近年、ブログの台頭と電子商取引サイトの急速な発展により、オンラインプロモーション方法の 1 つである...
[[408780]]コンセプトKafka は、LinkedIn によって最初に開発され、Scala ...
彼は公務員でしたが、インターネット会社を設立しました。彼は49歳で、中国のインターネット上で最年長の...
サブドメインとディレクトリに関しては、多くの人がそれらの違いを知らず、いつサブドメインを使用し、いつ...
Google は、データ分析、オープン インフラストラクチャ、オンライン接続の需要が高まっているアジ...
[[248564]]テクノロジー業界でまたもや大規模な合併と買収が起こっている。 IBM(Inter...
今日の物質主義の世界では、SEO テクノロジーは人々が利益を追求するための手段となっています。自社の...
東にフェイントをかけながら西を攻撃するマーケティングは、私たちの生活の中では非常に一般的です。ただし...
先週末、Ctrip.comは深刻なセキュリティリスクにさらされた。ユーザーの銀行カードのデータがロー...
ブランドとは何ですか?企業にとって、ブランドは無形の資産であり、消費者の心の中に残る企業の印象です。...