LinuxでのJavaからの仮想メモリの使用、メモリが多すぎる


259

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

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

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

とにかく、LinuxでJavaプロセスに使用する仮想メモリを構成できますか?

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

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

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


なぜ仮想メモリの使用に関心があるのですか?本当に心配したい場合は、常駐メモリの使用状況を調べ、次のコマンドを確認してください:free、ps、top。
basszero

2
埋め込まれているシステムで実行されている他のいくつかのアプリケーションがあります。また、システムには仮想メモリの制限があります。
MarioOrtegón、2009

ああ、悪魔は詳細に
バスゼロ

使用しているJavaの実装。湿原標準(OpenJDK以外)の無料のSun JREであるIIRCは、組み込みでの使用が許可されていません。
トム・ホーティン-タックライン09

「埋め込まれた」部分は使用しなかったと思います...メモリが限られていて、ハードウェアはカスタマイズされていますが、それでも標準のコンピュータです
MarioOrtegón

回答:


630

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

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


9
現在スワップアウトされているメモリの一部がRESメジャーから欠落していることを考慮する必要があります。したがって、RES値が低い可能性がありますが、それは、アプリケーションが非アクティブであり、ヒープの多くがディスクにスワップアウトされたためです。Javaはスワップに対して非常に悪い仕事をします:フルGCごとに、ヒープのほとんどがウォークされてコピーされるため、ヒープの大部分がスワップされていた場合、GCはすべてをメインメモリにロードする必要があります。
jrudolph、

1
すばらしい答えkdgregory!スワップ空間のないCFを使用して、組み込み環境で実行しています。したがって、あなたの回答に基づいて、私のVIRT、SWAP、およびnFLTの値はすべて、メモリマップファイルからのものです。SWAP値が、まだメモリにロードされていないページ、またはメモリからスワップアウトされたページ、またはその両方を表しているかどうかを知っていますか?スラッシングの可能性(連続したマップを入れ替える)をどのようにして把握できますか?
10

2
@Jeach-スワップが報告されたことに驚いたので、「traveling Linux」(Ubuntu 10.04のサムドライブ、スワップなし)を起動しました。私は「SWAP」の列を有効にすると、トップ、私は、Eclipseは509メートルを持っていました。その後、pmapで調べたところ、仮想空間の合計は650mでした。したがって、 "SWAP"の数値は、メモリにないページだけでなく、すべてのディスク上のページを表していると思います。
kdgregory

2
2番目の質問については、フラッシュカードから常にページを読み取っている場合は、IO待機時間(上部の概要に"%wa"として表示)が長くなっているはずです。ただし、これはあらゆるアクティビティ、特に書き込み(プログラムが何らかの処理を行うと想定)の場合は高くなることに注意してください。
kdgregory

1
> 1Mブロックはスレッドスタックです。4Kブロックの内容がわかりません。読み取り権限も書き込み権限もないとしてマークされている4Kブロックは、おそらくガードブロックです。スタックオーバーフロー時にこの領域にアクセスすると、障害が発生し、JVMはJava StackOverflowExceptionを生成して処理できます。これは、各メソッド呼び出しでスタックポインターをチェックするよりもはるかに安価です。権限が設定されていないガード領域は、他のコンテキストで使用されていることも確認できます。
eriksoe

38

Javaおよびglibc> = 2.10(Ubuntu> = 10.04、RHEL> = 6を含む)には既知の問題があります。

解決策は、この環境を設定することです。変数:

export MALLOC_ARENA_MAX=4

Tomcatを実行している場合は、これをTOMCAT_HOME/bin/setenv.shファイルに追加できます。

Dockerの場合、これをDockerfileに追加します

ENV MALLOC_ARENA_MAX=4

MALLOC_ARENA_MAXの設定に関するIBMの記事 https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en

このブログ投稿は言う

常駐メモリは、メモリリークやメモリの断片化と同様の方法で忍び寄ることが知られています。

未解決のJDKバグJDK-8193521もあります。「glibcはデフォルト設定でメモリを浪費します」

詳細については、GoogleまたはSOでMALLOC_ARENA_MAXを検索してください。

割り当てられたメモリの断片化が少ないように最適化するために、他のmallocオプションも調整したい場合があります。

# tune glibc memory allocation, optimize for low fragmentation
# limit the number of arenas
export MALLOC_ARENA_MAX=2
# disable dynamic mmap threshold, see M_MMAP_THRESHOLD in "man mallopt"
export MALLOC_MMAP_THRESHOLD_=131072
export MALLOC_TRIM_THRESHOLD_=131072
export MALLOC_TOP_PAD_=131072
export MALLOC_MMAP_MAX_=65536

この回答は、64ビットのUbuntu Serverで、「メモリ消費」が少ないTomEEサーバーを使用するのに役立ちました。IBMの記事へのリンクは本当に深い説明です。この良いヒントをありがとう!
MWiesner 2015年

1
JVMがネイティブメモリをリークする可能性があり、同様の症状が発生します。stackoverflow.com/a/35610063/166062を参照してください。閉じられていないGZIPInputStreamおよびGZIPOutputStreamインスタンスもリークの原因である可能性があります。
Lari Hotari 2016

3
:無制限ネイティブメモリの成長につながるのJava 8でのJVMのバグ、あるbugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8164293が、これは使用して、あなたに影響を及ぼしている場合- MALLOC_ARENA_MAXあなたの記憶成長を遅らせる、ないかもしれませんが、問題を完全に解決します。
outofcoffee 2017年

@LariHotariは、glibcとredhatバージョンを指摘するためのあなたの努力に本当に感謝しています
サム

2
Java 8u131には、関連するJVMバグJDK-8164293 bugs.openjdk.java.net/browse/JDK-8178124のバックポートされたバグ修正が含まれています 。
Lari Hotari 2017年

9

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 -client:32m。]

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

また、これを試して、VMがヒープの増加をあまり積極的にしないようにすることもできます。

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

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


7

Sun JVMはHotSpotに多くのメモリを必要とし、共有メモリのランタイムライブラリにマッピングされます。

メモリに問題がある場合は、埋め込みに適した別のJVMの使用を検討してください。IBMにはj9があり、GNUクラスパスライブラリを使用するオープンソース「jamvm」があります。また、SunはSunSPOTSでSqueak JVMを実行しているため、代替手段があります。


ホットスポットを無効にするオプションはありますか?
マリオ・オルテゴン2009年

たぶん。使用するJVMのコマンドラインオプションを確認してください。
するThorbjörnRavnアンデルセン

3

単なる考えですがulimit -vオプションの影響を確認することができます

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


それがまさに私の問題です。私のヒープは64Mに設定されていますが、linuxは204MBを予約しています。ulimitを204未満に設定すると、アプリケーションがまったく実行されません。
MarioOrtegón、2009

興味深い:ulimitを設定すると、他のプロセスに意図しない副作用が生じる可能性があり、アプリケーションが実行できない理由を説明します。
VonC、2009

問題は、Javaが仮想メモリを使用しない場合でも、この仮想メモリを予約する必要があることです。Windowsでは、使用される仮想メモリとXmx設定はかなり近くなっています。
MarioOrtegón、2009

JRockit JVMで試しましたか?
VonC、2009

JVMのメモリ割り当ては、ヒープ割り当てとパーマサイズの合計です(最初のサイズは-Xmsおよび-Xmxオプションを使用して修正できます)。-XX:PermSizeおよび-XX:MaxPermSizeを使用していくつかの設定を試しましたか? (デフォルトはJVMバージョンに応じて32MBから64MBまで)?
VonC、2009

3

リソースが限られているシステムのヒープサイスを減らす1つの方法は、-XX:MaxHeapFreeRatio変数をいじることです。これは通常70に設定され、GCが圧縮する前に解放されるヒープの最大パーセンテージです。これを低い値に設定すると、たとえばjvisualvmプロファイラーで、プログラムに通常より小さなヒープサイスが使用されることがわかります。

編集:-XX:MaxHeapFreeRatioに小さい値を設定するには、-XX:MinHeapFreeRatioなども設定する必要があります。

java -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=25 HelloWorld

EDIT2:1つはデフォルトのパラメーターを使用し、もう1つはパラメーターとして10と25を使用して、同じタスクを開始して実行する実際のアプリケーションの例を追加しました。後者の例では、Javaは理論的にはヒープを増やすためにより多くの時間を使用するはずですが、実際の速度の違いには気付きませんでした。

デフォルトのパラメーター

最後に、最大ヒープは905、使用済みヒープは378です。

MinHeap 10、MaxHeap 25

最後に、最大ヒープは722、使用済みヒープは378です。

私たちのアプリケーションはリモートデスクトップサーバー上で実行され、多くのユーザーが一度にそれを実行する可能性があるため、これには実際には少し影響があります。


1

SunのJava 1.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

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


1
私が抱えている問題は、ヒープサイズではなく、Linuxによって割り当てられる仮想メモリの量にあります
MarioOrtegónFeb

kdgregoryの説明を読んでください。ヒープサイズ、「新しいサイズ」、およびその他の設定可能なパラメータを減らすと、jvmが使用するREALメモリの量が減ります。
ポールトンブリン

彼には正当な問題があるかもしれません。一部のアプリケーション(私が書いたものなど)は1 GBのファイルをmmapし、一部のシステムは2 GBの仮想メモリしかないため、共有ライブラリでいっぱいになるものもあります。これが問題である場合は、DSOのランダム化を必ず無効にする必要があります。/ procにオプションがあります。
Zan Lynx 2010年

0

いいえ、VMに必要なメモリ量を構成することはできません。ただし、これは仮想メモリであり、常駐ではないため、実際に使用しなくても問題なくそのまま存在します。

あるいは、メモリフットプリントが小さいSun以外のJVMを試すこともできますが、ここではお勧めできません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.