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つの記事で学ぶ
オランダのサーバー業者 abelohost が新製品を発表しました。オランダの Windows VP...
ウェブサイトの最適化は、多くの分野からなる非常に包括的なタスクです。しかし、最も基本的な最適化作業と...
現在、ほとんどのウェブサイトの主な収入源は広告です。しかし、多くのウェブマスターは、広告収入が低いこ...
Baidu は新たな変更を加えました。検索結果を収集、共有、報告できるようになりました。これは LE...
最近、蘇寧クラウドと美団クラウドが相次いでクラウド市場からの撤退を発表し、それぞれ長年続いたクラウド...
[51CTO.com クイック翻訳] セキュリティの観点から、クラウド ストレージは存在すべきではあ...
tmhhost は現在、夏の特別オファーを開始しており、すべてのクラウド サーバーが 30% 割引さ...
クラウド コンピューティング サービス、特に Infrastructure as a Service...
[51CTO.comより引用] 最近、テンセントの新たな構造調整後の最大規模のカンファレンスである2...
rciol.com は香港に登録されているホスティング会社です (Smarton (Hong Kon...
今、Weibo はますます人気が高まっています。Weibo の魅力に感心するしかありません。140 ...
[[335450]]この記事はWeChatの公開アカウント「JavaKeeper」から転載したもので...
著者は以前、「企業ウェブサイトの最適化におけるいくつかの重要なポイントの簡単な分析」という記事を書い...
近年、WeChatの登場により、Sina Weiboに取って代わる傾向が見られます。多くのネットユー...
5月23日、広州で、広東省人民政府を指導機関として、広東省経済情報化委員会とテンセントが共催する20...