物理メモリが 8G あり、主に Java サービスを実行している一部のサーバーでは、システム メモリは次のように割り当てられます。Java サービスの JVM ヒープ サイズは 6G に設定され、監視プロセスが約 600m を占有し、Linux 自体は約 800m を使用します。
表面的には、物理メモリは十分なはずです。しかし、実際の動作では、次の図に示すように、大量の SWAP が使用されることになります (物理メモリが不足していることを示します)。 SWAP と GC が同時に発生すると深刻な JVM ラグが発生するため、メモリはどこに行くのかを尋ねる必要があります。 この問題を分析するには、JVM とオペレーティング システム間のメモリ関係を理解することが非常に重要です。次に、Linux と JVM 間のメモリ関係を中心に分析します。 1. Linuxとプロセスメモリモデル JVM は Linux システム上でプロセスとして実行されます。 Linux とプロセス間のメモリ関係を理解することは、JVM と Linux メモリの関係を理解するための基礎となります。次の図は、ハードウェア、システム、プロセス レベルでのメモリの関係の概要を示しています。 ハードウェアの観点から見ると、Linux システムのメモリ空間は、物理メモリと SWAP (ディスク上に配置) の 2 つの部分で構成されます。物理メモリは、Linux がアクティブなときに使用されるメインメモリ領域です。物理メモリが不足している場合、Linux は一時的に使用されていないメモリ データをディスク上の SWAP に配置して、使用可能なメモリ領域を増やします。 SWAP にあるデータを使用する必要がある場合は、まずメモリにスワップバックする必要があります。 JVM ランタイム領域の詳細な説明がありますので、一読することをお勧めします。 Linux システムの観点から見ると、ブート システムの BIN 領域を除いて、メモリ空間全体は主にカーネル メモリ (カーネル空間) とユーザー メモリ (ユーザー空間) の 2 つの部分に分かれています。 カーネルメモリは Linux 自体が使用するメモリ空間であり、主にプログラムのスケジューリング、メモリの割り当て、ハードウェアリソースの接続などのプログラムロジックに使用されます。 ユーザー メモリは、各プロセスに提供されるメイン スペースです。 Linux は各プロセスに同じ仮想メモリ空間を提供するため、プロセスは互いに独立し、干渉し合うことはありません。実装方法は仮想メモリ技術を使用することです。各プロセスに一定量の仮想メモリ空間を与え、仮想メモリが実際に使用される場合にのみ物理メモリを割り当てます。 下の図に示すように、32 ビット Linux システムの場合、通常、0 ~ 3G の仮想メモリ空間がユーザー空間として割り当てられ、3 ~ 4G の仮想メモリ空間がカーネル空間として割り当てられます。 64 ビット システムの分割も同様です。 プロセスから見ると、プロセスが直接アクセスできるユーザーメモリ(仮想メモリ空間)は、コード領域、データ領域、ヒープ領域、スタック領域、未使用領域の 5 つの部分に分かれています。 コード領域には、アプリケーションのマシン コードが格納されます。コードは操作中に変更できず、読み取り専用で固定サイズという特性があります。 データ領域には、アプリケーション内のグローバル データ、静的データ、および一部の定数文字列が格納され、そのサイズも固定されています。 ヒープは、実行時にプログラムによって動的に要求される領域です。プログラムの実行時に直接要求され、解放されるメモリ リソースです。 スタック領域は、関数パラメータ、一時変数、戻りアドレス、その他のデータを格納するために使用されます。 未使用領域は、新しいメモリ空間を割り当てるための予約領域です。 2. プロセスとJVMのメモリ空間 JVM は本質的にプロセスであるため、そのメモリ空間 (ランタイム データ領域とも呼ばれ、JMM との違いに注意) にもプロセスの一般的な特性があります。 Java での JVM メモリ管理の詳細については、この記事を参照してください。 しかし、JVM は通常のプロセスではありません。メモリ空間に多くの新機能が追加されました。主な理由は2つあります。 1. JVM は、システムコールの数を減らすことを目的として、もともとオペレーティングシステムの管理範囲に属していた多くのものを JVM に移植しました。 2. 読み取りおよび書き込み IO のシステム コールのオーバーヘッドを削減することを目的とした Java NIO。 JVM プロセスと通常のプロセスのメモリ モデルの比較を次の図に示します。 このモデルは JVM メモリ使用量の正確なモデルではないことに注意してください。オペレーティング システムの観点に重点を置いており、JVM の内部詳細の一部は省略しています (ただし、それらも重要です)。以下では、ユーザー メモリとカーネル メモリの 2 つの側面から JVM プロセスのメモリ特性について説明します。 1. ユーザーメモリ 上の図では、JVM プロセス モデルのコード領域とデータ領域が Java プログラムではなく、JVM 自体を参照していることを特に強調しています。通常のプロセス スタック領域は、通常、JVM 内のスレッド スタックとしてのみ使用されます。 JVM ヒープ領域と通常のプロセスの違いは非常に大きく、以下に詳しく説明します。 まずは***世代です。第一世代は、本質的には Java プログラムのコード領域とデータ領域です。 Java プログラム内のクラスは、定数プール、フィールド、メソッド データ、メソッド本体、コンストラクター、クラス内の特殊メソッド、インスタンス初期化、インターフェイス初期化など、領域全体のさまざまなデータ構造にロードされます。オペレーティング システムの場合、この領域はヒープ領域の一部です。 Java プログラムの場合、これはプログラム自体と静的リソースを保持する領域であり、JVM が Java プログラムを解釈して実行できるようにします。 次は新世代と旧世代です。新しい世代と古い世代は、Java プログラムによって実際に使用されるヒープ領域であり、主にメモリ オブジェクトを格納するために使用されます。しかし、その管理方法は通常のプロセスとは根本的に異なります。 C++ が新しい操作を実行するときなど、通常のプロセスが実行時にメモリ オブジェクトに領域を割り当てると、メモリ領域を割り当てるためのシステム コールがトリガーされます。オペレーティング システム スレッドは、オブジェクトのサイズに応じてスペースを割り当ててから戻ります。同時に、C++ が削除操作を実行する場合など、プログラムがオブジェクトを解放すると、オブジェクトが占有していた領域を再利用できることをオペレーティング システムに通知するシステム コールもトリガーされます。 JVM は一般的なプロセスとは異なる方法でメモリを使用します。 JVM は、Java プログラムのヒープ (新世代と旧世代に分かれている) として、メモリ領域全体 (具体的なサイズは JVM パラメータで調整可能) をオペレーティング システムに適用します。 Java プログラムが新しい操作を実行するなど、メモリ領域を要求すると、JVM はこの領域で Java プログラムに必要なサイズを割り当てます。Java プログラムは、このオブジェクトの領域を解放できる時期を JVM に通知する責任を負いません。ガベージ オブジェクトのメモリ領域は JVM によって再利用されます。 JVM のメモリ管理方法の利点は明らかで、まず、システム コールの数を減らすことができます。 JVM は、Java プログラムにメモリ領域を割り当てるときにオペレーティング システムの介入を必要としません。 Java ヒープ サイズが変更されたときに、メモリを申請するか、オペレーティング システムにリサイクルを通知するだけです。通常のプログラムでは、メモリ空間の割り当てとリサイクルのたびにシステム コールが必要になります。 2 番目は、メモリ リークを減らすことです。通常のプログラムは、メモリ領域の解放をオペレーティング システムに通知しません (またはすぐに通知しません)。これがメモリ リークの重要な原因の 1 つです。 JVM による統合管理により、プログラマーによるメモリ リークを回避できます。 *** は未使用領域であり、新しいメモリ領域を割り当てるための予約領域です。通常のプロセスの場合、この領域はヒープおよびスタック領域の申請と解放に使用できます。この領域はヒープメモリの割り当てごとに使用されるため、サイズは頻繁に変更されます。 JVM プロセスの場合、この領域はヒープ サイズとスレッド スタックを調整するときに使用され、ヒープ サイズの調整頻度は一般的に低いため、サイズは比較的安定しています。オペレーティング システムはこの領域のサイズを動的に調整します。通常、この領域には実際の物理メモリは割り当てられず、プロセスがこの領域内のヒープまたはスタック領域に適用することのみが許可されます。 2. カーネルメモリ 通常、アプリケーションはカーネル メモリを直接処理しません。カーネル メモリはオペレーティング システムによって管理および使用されます。ただし、Linux がパフォーマンスに重点を置いて改善するにつれて、いくつかの新機能により、アプリケーションがカーネル メモリを使用したり、カーネル空間にマップしたりできるようになりました。 Java NIO はこのような背景から生まれました。 Linux システムの新機能を最大限に活用し、Java プログラムの IO パフォーマンスを向上させます。 上の図は、Linux システムで Java NIO が使用するカーネル メモリの分布を示しています。 nio バッファには主に、nio が各種チャネルを使用するときに使用される ByteBuffer と、ByteBuffer.allocateDirector を使用して Java プログラムが積極的に割り当てる Buffer が含まれます。 PageCache では、nio が使用するメモリには主に、FileChannel.map メソッドを使用してファイルを開くために使用されるマップされたメモリと、FileChannel.transferTo および FileChannel.transferFrom に必要なキャッシュ (図では nio ファイルでマークされています) が含まれます。 次の図に示すように、NIO バッファとマップの使用状況は JMX を通じて監視できます。ただし、FileChannel の実装では、システム コールを通じてネイティブ PageCache が使用されます。このプロセスは Java に対して透過的であり、このメモリの使用量サイズを監視することはできません。 Linux および Java NIO は、主に不要なコピーを減らし、IO オペレーティング システム呼び出しのオーバーヘッドを削減するために、プログラムが使用するカーネル メモリにスペースを割り当てます。たとえば、共通方式と NIO を使用してディスク ファイル データをネットワーク カードに送信する場合、データ フローは次の図のように比較されます。 カーネル メモリとユーザー メモリ間でデータをコピーすると、リソースと時間が消費されます。上の図からわかるように、NIO はカーネル メモリとユーザー メモリ間のデータ コピーの数を 2 つ削減します。これは、Java NIO の高パフォーマンスを実現するための重要なメカニズムの 1 つです (もう 1 つは非同期非ブロッキングです)。 上記からわかるように、カーネル メモリも Java プログラムのパフォーマンスにとって非常に重要です。したがって、システム メモリを割り当てるときは、カーネル用に一定量の使用可能な領域を必ず残しておく必要があります。 3. 事例分析 1. メモリ割り当ての問題 上記の分析により、小さな領域を省略して、JVM が占有するメモリをまとめることができます。 JVM メモリ ≈ Java 第 1 世代 + Java ヒープ (新世代と旧世代) + スレッド スタック + Java NIO 記事の冒頭で提起した質問に戻ると、元のメモリ割り当ては、6g (Java ヒープ) + 600m (監視) + 800m (システム) であり、残りの約 600m のメモリは未割り当てです。 ここで、この 600m のメモリの割り当てを分析してみましょう。 Linux は、Linux の正常な動作に必要な約 200m を予約します。 Java サービス スレッドの数は 160 で、JVM のデフォルトのスレッド スタック サイズは 1m なので、160m のメモリが使用されます。 Java NIOバッファはJMXでチェックされ、最大200mを占有します。 Java サービスは NIO を使用して大量のファイルの読み取りと書き込みを行うため、PageCache を使用する必要があります。上で分析したように、現時点ではその規模を定量的に見積もることは困難です。 最初の 3 つの項目を合計すると 560m になるため、Linux の物理メモリが不足していると結論付けることができます。 注意深い人なら、冒頭で紹介した 2 つのサーバーのうち、1 つの SWAP が最大 2.16g を占有し、もう 1 つの SWAP が最大 871m を占有していることに気付くでしょう。しかし、私たちの記憶のギャップはそれほど大きくないようです。実際、これは SWAP と GC の同時使用によって発生します。下の図からわかるように、SWAP の使用と長い GC が同時に発生します。 SWAP と GC が同時に発生すると、GC 時間が非常に長くなり、JVM がひどく停止し、極端な場合にはサービスがクラッシュすることになります。理由は次のとおりです。JVM が GC を実行する場合、対応するヒープ パーティションの使用済みメモリをトラバースする必要があります。 GC 中にヒープ コンテンツの一部が SWAP にスワップされた場合、この部分を走査するときにメモリにスワップバックする必要があります。同時に、メモリ領域が不足しているため、メモリ内のヒープ領域の別の部分を SWAP にスワップする必要があります。したがって、ヒープ パーティションをトラバースするプロセスでは、(極端な場合には) ヒープ パーティション全体が順番に SWAP に書き込まれます。 Linux の SWAP 回復は遅れるため、大量の SWAP が占有されることになります。上記の問題は、ヒープ サイズを減らすか、物理メモリを増やすことで解決できます。 したがって、次のような結論に達しました。Linux システムに Java サービスを展開する場合は、メモリ割り当てで SWAP の使用を避ける必要があります。具体的な割り当て方法では、さまざまなシナリオで Java 第 1 世代、Java ヒープ (新世代と旧世代)、スレッド スタック、および Java NIO によって使用されるメモリに対する JVM の要件を総合的に考慮する必要があります。 2. メモリリークの問題 もう 1 つのケースは、8 GB のメモリを搭載したサーバーで、Linux が 800 GB を使用し、監視プロセスが 600 GB を使用し、ヒープ サイズが 4 GB に設定されているというものです。システムには約 2.5 GB の使用可能なメモリがありますが、大量の SWAP も占有されています。 この問題の分析は次のとおりです。 1 このシナリオでは、Java 第 1 世代、Java ヒープ (新世代と旧世代)、およびスレッド スタックによって使用されるメモリは基本的に固定されています。したがって、メモリ使用量が過剰になる原因は Java NIO にあります。 2 以前のモデルによると、Java NIO が使用するメモリは、主に Linux カーネル メモリの System 領域と PageCache 領域に分散されています。監視記録を見ると、下図のように、SWAPが発生する前、つまり物理メモリが不足しているときに、PageCache が急激に縮小していることがわかります。したがって、システム領域で Java NIO バッファのメモリ リークを見つけることができます。 3 NIO の DirectByteBuffer は GC の後半でリサイクルされる必要があるため、DirectByteBuffer を継続的に適用するプログラムでは、通常、長期間 FullGC が行われないことによる古い領域で参照される DirectByteBuffer のメモリ リークを回避するために System.gc() を呼び出す必要があります。この分析から、2 つの理由が考えられます。1 つ目は、Java プログラムが必要なときに System.gc() を呼び出さなかったことです。 2 番目に、System.gc() が無効になりました。 4 *** JVM 起動パラメータと Java プログラムの DirectByteBuffer の使用状況を確認する必要があります。この場合、JVM の起動パラメータを確認すると、-XX:+DisableExplicitGC が有効になっていることがわかり、System.gc() が無効になります。 IV.結論 この記事では、Linux と JVM のメモリ関係を詳細に分析し、メモリの使用における一般的なプロセスと JVM プロセスの類似点と相違点を比較します。これらの特性を理解することは、Linux システムのメモリ割り当て、JVM のチューニング、Java プログラムの最適化に役立ちます。スペースの制限により、ここでは 2 つの事例のみを取り上げますが、これが今後の議論の出発点となることを期待しています。 |
<<: Google の Anthos は、AWS や Azure のハイブリッド クラウドとどう違うのでしょうか?
同紙は、ウェブサイト管理バックエンドリンクを通じて映画・テレビ作品のシードファイルインデックスアドレ...
米国オハイオ州に拠点を置く企業 ftpit (まもなく設立 2 周年) は、ニューヨークのデータ セ...
月収10万元の起業の夢を実現するミニプログラム起業支援プラン「中国の鯉になってほしい」という微博の投...
Baidu が最近発表した Web2.0 スパム対策戦略は、私たちウェブマスターに大きなインスピレー...
コンテンツ起業にとって、暖を取るために「お金を燃やして」脂肪を蓄えることは難しく、多くの企業が実践で...
長年にわたる情報構築を経て、中国のデジタル政府業務は新たな段階に入った。国務院弁公庁が昨年発表した「...
分散ロックとは何ですか?分散ロックは、分散システム間の共有リソースへの同期アクセスを制御する方法です...
北京時間7月1日、海外メディアの報道によると、2012年6月はテクノロジー業界にとって新しい時代の始...
[51CTO.comからのオリジナル記事] モノのインターネットと5Gネットワークの発展に伴い、...
ショートビデオ、セルフメディア、インフルエンサーのためのワンストップサービスWeibo は、企業のニ...
最近、一部の団体が「拡散お願いします!値引き交渉は個人情報を盗みます…値引き交渉には絶対に手を出さな...
これは繰り返し提起されてきた質問なので、私の記事のタイトルは「バックリンクについてもう一度話しましょ...
ショートビデオ、セルフメディア、インフルエンサーのためのワンストップサービスフォロワーを早く増やす方...
dynaboot は設立から非常に時間が経った VPS 業者と言えます。ドメイン名は 2017 年 ...
SEO 企業で働いたことがあるウェブマスターが何人いるかはわかりません。私は以前、医療投資会社で働い...