1. ホーム

[解決済み】LinuxでJavaの仮想メモリ使用量、メモリ使用量が多すぎる。

2022-04-16 02:41:55

質問

Linuxで動作するJavaアプリケーションに問題があります。

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

予約された仮想メモリは、私が理解する限り、いずれにせよ使用されません。 OutOfMemoryError が投げられます。 同じアプリケーションをWindowsで実行したところ、仮想メモリサイズとヒープサイズが似ていることがわかりました。

LinuxのJavaプロセスで使用するVirtual Memoryを設定する方法はありますか?

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

編集2 : 使用方法 ulimit -v は、仮想メモリの量を制限することができます。 設定されたサイズが204MB以下であれば、204MBも必要なく64MBしか必要ないにもかかわらず、アプリケーションは実行されないのです。 そこで、なぜJavaがこれほど多くの仮想メモリを必要とするのか、その理由を理解したいのです。 これは変更できるのでしょうか?

編集3 : 他にもいくつかのアプリケーションが動作している、組み込み型のシステムです。そして、このシステムには仮想メモリの制限があります(コメントより、重要な詳細)。

解決方法は?

これはJavaに対する長年の不満ですが、ほとんど意味がなく、大抵は間違った情報を見ていることに基づいています。通常の言い回しは、"Java上のHello Worldは10メガバイトかかる!のようなものです。さて、64ビットのJVM上のHello Worldが4ギガバイト以上かかると主張する方法があります...少なくともある測定方法では。

java -Xms1024m -Xmx4096m com.example.Hello

メモリのさまざまな測定方法

Linuxでは トップ コマンドを実行すると、メモリに関するいくつかの異なる数値が表示されます。以下は、Hello Worldの例について書かれている内容です。

  pid user pr ni virt res shr s %cpu %mem time+ command
 2120 kgregory 20 0 4373m 15m 7152 S 0 0.2 0:00.10 java

  • VIRTは、仮想メモリ空間:仮想メモリマップ(下記参照)内のすべてのものの合計です。そうでない場合を除き、ほとんど意味がありません(下記参照)。
  • RESは常駐セットサイズ:現在RAMに常駐しているページ数です。ほとんどすべての場合において、これは "too big." と言うときに使うべき唯一の数字ですが、特にJavaについて話すときには、まだあまり良い数字ではありません。
  • SHRは、他のプロセスと共有される常駐メモリの量です。Javaプロセスの場合、これは通常、共有ライブラリとメモリマップドJARfilesに限定されます。この例では、1つのJavaプロセスしか起動していなかったので、7kはOSが使用しているライブラリの結果だと思われます。
  • SWAPはデフォルトではオンになっていないため、ここには表示されていません。これは、現在ディスク上に常駐している仮想メモリの量を示しています。 実際にスワップスペースにあるかどうか . OSはRAMにアクティブなページを維持するのが得意で、スワップの治療法は(1)メモリを買うか、(2)プロセス数を減らすしかないので、この数値は無視するのが一番です。

Windowsタスクマネージャの状況は、もう少し複雑です。Windows XP では、"Memory Usage" と "Virtual Memory Size" のカラムが存在しますが 公式ドキュメント は、その意味するところが不明です。Windows VistaとWindows 7では、さらに列が追加され、それらは実際に 文書化 . これらのうち、quot;Working Set"の測定値が最も有用で、LinuxでのRESとSHRの合計にほぼ相当します。

仮想メモリーマップを理解する

プロセスによって消費される仮想メモリは、プロセスのメモリマップにあるすべてのものの合計です。これには、データ(例えば、Javaヒープ)だけでなく、プログラムが使用するすべての共有ライブラリとメモリマップドファイルが含まれます。Linuxでは pmap コマンドを実行すると、プロセス空間にマッピングされたすべてのものを見ることができます(ここからは、私が使っているのはLinuxなので、Linuxだけを参照することにします。) 以下は "Hello World" プログラムのメモリマップからの抜粋です。メモリマップ全体は100行以上あり、1000行のリストを持つことも珍しくありません。

0000000040000000 36K r-x-- /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 r-x-- /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 r-x-- /lib/x86_64-linux-gnu/libc-2.13.so ...
00007fa1f3634000 2044K ----- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3833000 16K r-x-- /lib/x86_64-linux-gnu/libc-2.13.so。
00007fa1f3837000 4K rwx-- /lib/x86_64-linux-gnu/libc-2.13.so
...

各行は、セグメントの仮想メモリアドレスから始まります。その後に、セグメントサイズ、パーミッション、そしてセグメントのソースが続きます。最後の項目は、ファイルまたは "anon"で、これは ミリマップ .

上から順に、次のようになります。

  • JVMローダー(つまり、あなたが java ). これは非常に小さいもので、実際のJVMコードが格納されている共有ライブラリをロードするだけです。
  • Javaヒープと内部データを保持するアノン・ブロックの束です。これはSun JVMなので、ヒープは複数の世代に分割され、それぞれが独自のメモリー・ブロックになっています。JVMが仮想メモリ空間を割り当てる際、その割り当ては -Xmx このため、連続したヒープを持つことができます。このため、連続したヒープを持つことができます。 -Xms の値は、プログラム開始時にヒープがどの程度使用されているかを示すために内部的に使用され、その限界に近づくとガベージコレクションをトリガーします。
  • メモリマップド JAR ファイル、この場合は "JDK クラスを保持するファイル。メモリマップされた JAR は、その中のファイルに非常に効率的にアクセスできます(毎回最初から読み込むよりも)。Sun JVMはクラスパス上のすべてのJARをメモリマップします。アプリケーションコードがJARにアクセスする必要がある場合、それをメモリマップすることもできます。
  • 2スレッド分のスレッド単位のデータ。1Mブロックはスレッドスタック。4K ブロックについて良い説明がなかったのですが、@ericsoe 氏はこれを "guard block" と特定しました:これは読み取り/書き込みの権限を持っていないので、アクセスするとセグメントフォルトを引き起こします。 StackOverFlowError . 実際のアプリでは、このようなエントリーがメモリマップ上で何十個も何百個も繰り返されることになります。
  • 実際のJVMコードを保持する共有ライブラリの1つです。このうちのいくつかがあります。
  • C言語標準ライブラリの共有ライブラリです。これは、JVMが読み込む、厳密にはJavaの一部ではない多くのもののうちの1つに過ぎません。

各共有ライブラリには、ライブラリコードを含む読み込み専用のセグメントと、ライブラリのプロセス単位のグローバルデータを含む読み書き可能なセグメントの少なくとも2つがあります(パーミッションのないセグメントが何かはわかりません;x64 Linuxでしか見たことがありません)。ライブラリの読み取り専用部分は、ライブラリを使用するすべてのプロセス間で共有することができます; 例えば。 libc は、共有可能な仮想メモリ領域が1.5Mである。

仮想メモリサイズが重要なのはどんなとき?

仮想メモリマップには、多くのものが含まれています。一部は読み取り専用で、一部は共有され、一部は割り当てられたまま触れられることがありません(例えば、この例では4Gbのヒープのほとんどすべて)。しかし、オペレーティングシステムは賢いので、必要なものだけをロードするので、仮想メモリの大きさはほとんど関係ありません。

仮想メモリのサイズが重要になるのは、32ビットOSを使用している場合で、プロセスアドレス空間が2Gb(場合によっては3Gb)しか割り当てられない場合です。この場合、希少なリソースを扱うことになり、大きなファイルをメモリマッピングしたり、多くのスレッドを作成するためにヒープサイズを小さくするなど、トレードオフをしなければならない可能性があります。

しかし、64ビットマシンの普及を考えると、仮想メモリサイズがまったく関係のない統計になる日もそう遠くはないでしょう。

レジデントセットサイズはいつ重要か?

レジデントセットサイズとは、仮想メモリ空間のうち、実際にRAMに存在する部分のことです。RSSが物理メモリ全体の大部分を占めるようになった場合、心配になるかもしれません。RSSが物理メモリ全体を占有するようになり、システムがスワップを開始した場合、心配する時期は過ぎています。

しかし、RSSは、特に負荷の軽いマシンでは、誤解を招くこともあります。オペレーティングシステムは、プロセスによって使用されるページを再利用するために多くの労力を費やすことはありません。そうすることで得られる利益はほとんどなく、将来そのプロセスがページに触れた場合、高価なページフォルトが発生する可能性があるからです。その結果、RSS 統計には、アクティブに使用されていないページが多く含まれる可能性があります。

ボトムライン

スワップしているのでなければ、様々なメモリ統計が何を語っているか過度に気にする必要はありません。ただし、RSSが増え続けている場合は、ある種のメモリリークを示唆している可能性があります。

Javaプログラムでは、ヒープで何が起こっているかに注意を払うことがはるかに重要です。消費されるスペースの総量は重要であり、それを減らすためにできることはいくつかあります。それよりも重要なのは、ガベージコレクションに費やす時間の長さと、ヒープのどの部分が収集されるかということです。

ディスク(つまりデータベース)へのアクセスは高価であり、メモリは安価です。どちらかを交換できるのであれば、そうしてください。