286

Linux で実行している Java アプリケーションに問題があります。

デフォルトの最大ヒープ サイズ (64 MB) を使用してアプリケーションを起動すると、tops アプリケーションを使用すると、240 MB の仮想メモリがアプリケーションに割り当てられていることがわかります。これにより、コンピューター上の他のソフトウェアで問題が発生し、リソースが比較的制限されます。

OutOfMemoryErrorヒープ制限に達すると anがスローされるため、私が理解している限り、予約済みの仮想メモリはとにかく使用されません。Windows で同じアプリケーションを実行したところ、仮想メモリ サイズとヒープ サイズが似ていることがわかりました。

Linux で Java プロセスに使用する仮想メモリを構成する方法はありますか?

編集1 :問題はヒープではありません。問題は、たとえば 128 MB のヒープを設定した場合でも、Linux は 210 MB の仮想メモリを割り当てますが、これはまったく必要ありません。**

編集 2 : を使用ulimit -vすると、仮想メモリの量を制限できます。サイズ セットが 204 MB 未満の場合、アプリケーションは 204 MB を必要とせず、64 MB しか必要としない場合でも実行されません。ですから、Java が大量の仮想メモリを必要とする理由を理解したいと思います。これは変更できますか?

編集 3 : 埋め込まれているシステムで実行されている他のいくつかのアプリケーションがあります。また、システムには仮想メモリの制限があります(コメント、重要な詳細から)。

4

8 に答える 8

685

これは 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 であるため、ヒープは複数の世代に分割され、それぞれが独自のメモリ ブロックになります。-XmxJVM は値に基づいて仮想メモリ空​​間を割り当てることに注意してください。これにより、連続したヒープを持つことができます。この-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 プログラムでは、ヒープで何が起こっているかに注意を払うことがはるかに重要です。消費されるスペースの総量は重要であり、それを削減するために実行できるいくつかの手順があります。さらに重要なのは、ガベージ コレクションに費やす時間と、ヒープのどの部分が収集されているかです。

ディスク (データベースなど) へのアクセスは高価ですが、メモリは安価です。一方を他方と交換できる場合は、そうしてください。

于 2009-02-18T15:17:07.777 に答える
10

Java プロセスに割り当てられたメモリの量は、私が予想していたものとほとんど同じです。組み込み/メモリが制限されたシステムでJavaを実行すると、同様の問題が発生しました。任意のVM 制限でアプリケーションを実行したり、適切な量のスワップを持たないシステムでアプリケーションを実行すると、壊れる傾向があります。これは、リソースが限られたシステムで使用するように設計されていない多くの最新のアプリの性質のようです。

JVM のメモリ フットプリントを試して制限できるいくつかのオプションがあります。これにより、仮想メモリのフットプリントが削減される可能性があります。

-XX:ReservedCodeCacheSize=32m 予約済みコード キャッシュ サイズ (バイト単位) - 最大コード キャッシュ サイズ。[Solaris 64 ビット、amd64、および -server x86: 48m; 1.5.0_06 以前では、Solaris 64 ビットおよび and64: 1024m.]

-XX:MaxPermSize=64m 永久世代のサイズ。[5.0 以降: 64 ビット VM は 30% 大きくスケーリングされます。1.4 amd64: 96m; 1.3.1 - クライアント: 32m.]

また、-Xmx (最大ヒープ サイズ) を、アプリケーションの実際のピーク メモリ使用量にできるだけ近い値に設定する必要もあります。JVM のデフォルトの動作は、最大まで拡張するたびにヒープ サイズを2 倍にすることだと思います。32M ヒープで開始し、アプリが 65M に達した場合、ヒープは 32M -> 64M -> 128M に増加します。

また、VM がヒープの拡大をあまり積極的に行わないようにするために、次のことを試すこともできます。

-XX:MinHeapFreeRatio=40 拡張を避けるための GC 後のヒープの最小パーセンテージ。

また、数年前にこれを試したときのことを思い出すと、読み込まれたネイティブ ライブラリの数が最小フットプリントに大きな影響を与えました。正しく思い出せば、java.net.Socket をロードすると 15M 以上追加されます (おそらく覚えていません)。

于 2009-02-18T16:21:52.190 に答える
4

思いつきですがulimit -vオプションの影響を確認してみてはいかがでしょうか。

すべてのプロセスで使用できるアドレス空間が制限されるため、これは実際の解決策ではありませんが、限られた仮想メモリでアプリケーションの動作を確認できるようになります。

于 2009-02-18T14:51:39.430 に答える
1

Sunのjava1.4には、メモリサイズを制御するための次の引数があります。

-Xmsnメモリ割り当てプールの初期サイズをバイト単位で指定します。この値は、1MBより大きい1024の倍数である必要があります。文字kまたはKを追加してキロバイトを示し、mまたはMを追加してメガバイトを示します。デフォルト値は2MBです。例:

           -Xms6291456
           -Xms6144k
           -Xms6m

-Xmxnメモリ割り当てプールの最大サイズをバイト単位で指定します。この値は、2MBより大きい1024の倍数である必要があります。文字kまたはKを追加してキロバイトを示し、mまたはMを追加してメガバイトを示します。デフォルト値は64MBです。例:

           -Xmx83886080
           -Xmx81920k
           -Xmx80m

http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/java.html

Java5および6にはさらにいくつかあります。http://java.sun.com/javase/technologies/hotspot/vmoptions.jspを参照してください

于 2009-02-18T14:35:14.820 に答える
0

いいえ、VM に必要なメモリ量を構成することはできません。ただし、これは仮想メモリであり、常駐ではないことに注意してください。実際に使用されていない場合でも、害はありません。

別の方法として、メモリ フットプリントが小さい Sun の JVM よりも他の JVM を試すこともできますが、ここではお勧めできません。

于 2009-02-18T14:28:52.307 に答える