これは Java に対する長年の不満でしたが、ほとんど意味がなく、通常は間違った情報に基づいています。通常の言い回しは、「Java の Hello World は 10 メガバイトかかります! なぜそれが必要なのですか?」のようなものです。さて、これは 64 ビット JVM で Hello World が 4 ギガバイトを超えると主張する方法です...少なくとも 1 つの測定形式によって。
java -Xms1024m -Xmx4096m com.example.Hello
メモリを測定するさまざまな方法
Linux では、topコマンドを実行すると、メモリのいくつかの異なる数値が得られます。Hello World の例については、次のように説明されています。
PID ユーザー PR NI VIRT RES SHR S %CPU %MEM TIME+ コマンド
2120 kgregory 20 0 4373m 15m 7152 S 0 0.2 0:00.10 Java
- VIRT は仮想メモリ空間です。仮想メモリ マップ内のすべての合計です (以下を参照)。そうでない場合を除いて、ほとんど意味がありません (以下を参照)。
- RES は、常駐セット サイズ (RAM に現在常駐しているページの数) です。ほとんどの場合、これは「大きすぎる」と言うときに使用する唯一の数値です。しかし、特にJavaについて話すときは、まだあまり良い数字ではありません.
- SHR は、他のプロセスと共有される常駐メモリの量です。Java プロセスの場合、これは通常、共有ライブラリとメモリ マップされた JAR ファイルに限定されます。この例では、1 つの Java プロセスしか実行していないため、7k は OS によって使用されるライブラリの結果であると思われます。
- SWAP はデフォルトではオンになっていないため、ここには表示されません。これは、実際にスワップ スペースにあるかどうかに関係なく、現在ディスク上に存在する仮想メモリの量を示します。OS はアクティブなページを RAM に保持することに非常に優れており、スワッピングの唯一の解決策は (1) メモリを追加購入するか、(2) プロセス数を減らすことです。そのため、この数を無視することをお勧めします。
Windows タスク マネージャーの状況はもう少し複雑です。Windows XP では、"メモリ使用量" と "仮想メモリ サイズ" の列がありますが、公式ドキュメントではそれらの意味については言及されていません。Windows Vista と Windows 7 ではさらに列が追加されており、実際に文書化されています。これらのうち、「ワーキング セット」測定が最も有用です。これは、Linux の RES と SHR の合計にほぼ相当します。
仮想メモリ マップについて
プロセスによって消費される仮想メモリは、プロセス メモリ マップにあるすべてのものの合計です。これには、データ (Java ヒープなど) だけでなく、プログラムが使用するすべての共有ライブラリとメモリ マップ ファイルも含まれます。Linux では、 pmapコマンドを使用して、プロセス空間にマッピングされたすべてのものを表示できます (ここからは Linux についてのみ言及します。これは私が使用しているものなので、Linux に相当するツールがあると確信しています。ウィンドウズ)。これは、「Hello World」プログラムのメモリ マップからの抜粋です。メモリ マップ全体の長さは 100 行を超えており、1,000 行のリストがあることも珍しくありません。
0000000040000000 36K rx-- /usr/local/java/jdk-1.6-x64/bin/java
0000000040108000 8K rwx-- /usr/local/java/jdk-1.6-x64/bin/java
0000000040eba000 676K rwx-- [アノン]
00000006fae00000 21248K rwx-- [アノン]
00000006fc2c0000 62720K rwx-- [アノン]
0000000700000000 699072K rwx-- [アノン]
000000072aab0000 2097152K rwx-- [アノン]
00000007aaab0000 349504K rwx-- [アノン]
00000007c0000000 1048576K rwx-- [アノン]
...
00007fa1ed00d000 1652K r-xs- /usr/local/java/jdk-1.6-x64/jre/lib/rt.jar
...
00007fa1ed1d3000 1024K rwx-- [アノン]
00007fa1ed2d3000 4K ----- [アノン]
00007fa1ed2d4000 1024K rwx-- [アノン]
00007fa1ed3d4000 4K ----- [アノン]
...
00007fa1f20d3000 164K rx-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f20fc000 1020K ----- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f21fb000 28K rwx-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
...
00007fa1f34aa000 1576K rx-- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3634000 2044K ----- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3833000 16K rx-- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3837000 4K rwx-- /lib/x86_64-linux-gnu/libc-2.13.so
...
フォーマットの簡単な説明: 各行は、セグメントの仮想メモリ アドレスで始まります。これに続いて、セグメント サイズ、権限、およびセグメントのソースが続きます。この最後の項目は、ファイルまたは「anon」のいずれかで、mmapによって割り当てられたメモリのブロックを示します。
上から順に、
- JVM ローダー (つまり、 を入力すると実行されるプログラム
java
)。これは非常に小さいです。実際の JVM コードが保存されている共有ライブラリにロードするだけです。
- Java ヒープと内部データを保持する一連の anon ブロック。これは Sun JVM であるため、ヒープは複数の世代に分割され、それぞれが独自のメモリ ブロックになります。
-Xmx
JVM は値に基づいて仮想メモリ空間を割り当てることに注意してください。これにより、連続したヒープを持つことができます。この-Xms
値は、プログラムの開始時に「使用中」のヒープの量を示し、その制限に近づくとガベージ コレクションをトリガーするために内部的に使用されます。
- メモリマップされた JAR ファイル。この場合は「JDK クラス」を保持するファイルです。JAR をメモリ マップすると、その中のファイルに非常に効率的にアクセスできます (毎回最初から読み取るのではなく)。Sun JVM は、クラスパス上のすべての JAR をメモリ マップします。アプリケーション コードが JAR にアクセスする必要がある場合は、それをメモリ マップすることもできます。
- 2 つのスレッドのスレッドごとのデータ。1M ブロックはスレッド スタックです。私は 4k ブロックについて適切な説明を持っていませんでしたが、@ericsoe はそれを「ガード ブロック」と特定しました。これには読み取り/書き込み権限がないため、アクセスするとセグメント フォールトが発生し、JVM がそれをキャッチして変換しますそれをに
StackOverFlowError
。実際のアプリの場合、これらのエントリがメモリ マップを通じて繰り返される数百とまではいかなくても、数十が表示されます。
- 実際の JVM コードを保持する共有ライブラリの 1 つ。これらはいくつかあります。
- C 標準ライブラリの共有ライブラリ。これは、厳密には Java の一部ではない、JVM がロードする多くのものの 1 つにすぎません。
共有ライブラリは特に興味深いものです。各共有ライブラリには、少なくとも 2 つのセグメントがあります。ライブラリ コードを含む読み取り専用セグメントと、ライブラリのプロセスごとのグローバル データを含む読み書き可能セグメントです (パーミッションのないセグメントは、x64 Linux でしか見たことがありません)。ライブラリの読み取り専用部分は、ライブラリを使用するすべてのプロセス間で共有できます。たとえば、libc
共有可能な 1.5M の仮想メモリ空間があります。
仮想メモリ サイズが重要な場合
仮想メモリマップには多くのものが含まれています。一部は読み取り専用で、一部は共有され、一部は割り当てられているが決して操作されません (たとえば、この例では 4Gb のヒープのほぼすべて)。しかし、オペレーティング システムは必要なものだけをロードするほどスマートなので、仮想メモリのサイズはほとんど関係ありません。
仮想メモリのサイズが重要なのは、32 ビット オペレーティング システムで実行している場合で、2Gb (場合によっては 3Gb) のプロセス アドレス空間しか割り当てることができません。その場合、希少なリソースを扱っており、大きなファイルをメモリ マップしたり、多くのスレッドを作成したりするためにヒープ サイズを縮小するなどのトレードオフが必要になる場合があります。
しかし、64 ビット マシンがどこにでもあることを考えると、仮想メモリ サイズが完全に無関係な統計になるまでそう長くはかからないと思います。
常駐セットのサイズが重要なのはいつですか?
常駐セットのサイズは、実際に RAM にある仮想メモリ空間の部分です。RSS が物理メモリ全体のかなりの部分を占めるようになったら、心配し始める時期かもしれません。RSS がすべての物理メモリを占有するようになり、システムがスワッピングを開始した場合は、心配する必要はありません。
しかし、特に負荷の軽いマシンでは、RSS も誤解を招きます。オペレーティング システムは、プロセスによって使用されるページを再利用するために多くの労力を費やすことはありません。そうすることで得られるメリットはほとんどなく、プロセスが将来ページにアクセスした場合に、コストのかかるページ フォールトが発生する可能性があります。その結果、RSS 統計には、アクティブに使用されていないページが多数含まれる場合があります。
結論
スワッピングをしている場合を除き、さまざまなメモリ統計が何を示しているかについて過度に心配する必要はありません。増え続ける RSS は、何らかのメモリ リークを示している可能性があることに注意してください。
Java プログラムでは、ヒープで何が起こっているかに注意を払うことがはるかに重要です。消費されるスペースの総量は重要であり、それを削減するために実行できるいくつかの手順があります。さらに重要なのは、ガベージ コレクションに費やす時間と、ヒープのどの部分が収集されているかです。
ディスク (データベースなど) へのアクセスは高価ですが、メモリは安価です。一方を他方と交換できる場合は、そうしてください。