これは長年の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ファイルに限定されます。この例では、Javaプロセスが1つしか実行されていなかったため、7kはOSが使用するライブラリの結果であると思われます。
- SWAPはデフォルトではオンになっていないため、ここには表示されません。実際にスワップ領域にあるかどうかに関係なく、現在ディスクに常駐している仮想メモリの量を示します。OSはRAMにアクティブページを保持することに関して非常に優れており、スワッピングの唯一の解決策は、(1)メモリを追加購入するか、(2)プロセス数を減らすことです。したがって、この数は無視するのが最善です。
Windowsタスクマネージャの状況はもう少し複雑です。Windows XPでは、「メモリ使用量」と「仮想メモリサイズ」の列がありますが、公式ドキュメントにはそれらの意味については記載されていません。Windows VistaとWindows 7では列が追加され、実際に文書化されています。これらのうち、「ワーキングセット」測定が最も有用です。LinuxのRESとSHRの合計にほぼ対応します。
仮想メモリマップについて
プロセスによって消費される仮想メモリは、プロセスメモリマップにあるすべての合計です。これには、データ(Javaヒープなど)だけでなく、プログラムで使用されるすべての共有ライブラリとメモリマップファイルも含まれます。Linuxでは、pmapコマンドを使用して、プロセス空間にマップされたすべてのものを表示できます(これ以降は、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-- [anon]
00000006fae00000 21248K rwx-- [anon]
00000006fc2c0000 62720K rwx-- [anon]
0000000700000000 699072K rwx-- [anon]
000000072aab0000 2097152K rwx-- [anon]
00000007aaab0000 349504K rwx-- [anon]
00000007c0000000 1048576K rwx-- [anon]
...
00007fa1ed00d000 1652K r-xs- /usr/local/java/jdk-1.6-x64/jre/lib/rt.jar
...
00007fa1ed1d3000 1024K rwx-- [anon]
00007fa1ed2d3000 4K ----- [anon]
00007fa1ed2d4000 1024K rwx-- [anon]
00007fa1ed3d4000 4K ----- [anon]
...
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であるため、ヒープは複数の世代に分割され、それぞれが独自のメモリブロックです。JVMは
-Xmx
値に基づいて仮想メモリ空間を割り当てることに注意してください。これにより、ヒープを連続させることができます。この-Xms
値は内部的に使用され、プログラムの起動時にヒープがどの程度「使用中」であるかを示し、その制限に近づくとガベージコレクションをトリガーします。
- メモリマップされたJARファイル(この場合は「JDKクラス」を保持するファイル)。JARをメモリマップすると、JAR内のファイルに非常に効率的にアクセスできます(毎回最初から読み取るのではありません)。Sun JVMは、クラスパス上のすべてのJARをメモリマップします。アプリケーションコードが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プログラムでは、ヒープで何が起こっているかに注意を払うことがはるかに重要です。消費されるスペースの総量は重要であり、それを減らすために実行できるいくつかのステップがあります。さらに重要なのは、ガベージコレクションに費やす時間と、ヒープのどの部分が収集されるかです。
ディスク(データベース)へのアクセスはコストが高く、メモリも安価です。もう一方と交換できる場合は、交換してください。