web-dev-qa-db-ja.com

LinuxでのJavaの仮想メモリ使用量、使用されるメモリが多すぎる

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

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

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

とにかく、LinuxでJavaプロセスに使用中の仮想メモリを設定できるのですか?

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

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

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

237
Mario Ortegón

これはJavaに対する長年の不満でしたが、ほとんど意味がなく、通常は間違った情報を見ることに基づいています。通常の言い回しは、「Hello World on Javaは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のみを参照します。 Windows用の同等のツールがあることを確認してください)。 「Hello World」プログラムのメモリマップからの抜粋です。メモリマップ全体の長さは100行を超えており、1000行のリストがあることも珍しくありません。

 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ヒープと内部データを保持する一連の匿名ブロック。これはSun JVMであるため、ヒープは複数の世代に分割されます。各世代は独自のメモリブロックです。 JVMは-Xmx値に基づいて仮想メモリ空​​間を割り当てることに注意してください。これにより、連続したヒープを持つことができます。 -Xms値は内部的に使用され、プログラムの開始時にヒープがどれだけ「使用中」であるかを示し、その制限に近づくとガベージコレクションをトリガーします。
  • メモリマップされたJARファイル、この場合は「JDKクラス」を保持するファイル。 JARをメモリマップすると、JAR内のファイルに非常に効率的にアクセスできます(毎回最初から読み込むのとは対照的です)。 Sun JVMは、クラスパス上のすべてのJARをメモリマップします。アプリケーションコードがJARにアクセスする必要がある場合は、メモリマップすることもできます。
  • 2つのスレッドのスレッドごとのデータ。 1Mブロックはスレッドスタックです。 4Kブロックに何が入るかはわかりません。実際のアプリの場合、これらのエントリの数百ではないにしても、数十個がメモリマップで繰り返されます。
  • 実際のJVMコードを保持する共有ライブラリの1つ。これらのいくつかがあります。
  • C標準ライブラリの共有ライブラリ。これは、厳密にはJavaの一部ではないJVMがロードする多くの項目の1つにすぎません。

共有ライブラリは特に興味深いものです。各共有ライブラリには少なくとも2つのセグメントがあります。ライブラリコードを含む読み取り専用セグメントと、ライブラリのプロセスごとのグローバルデータを含む読み取り/書き込みセグメントです(許可のないセグメントは、x64 Linuxでのみ見たことがあります)。ライブラリの読み取り専用部分は、ライブラリを使用するすべてのプロセス間で共有できます。たとえば、libcには、共有可能な仮想メモリ空​​間が1.5Mあります。

仮想メモリサイズはいつ重要ですか?

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

仮想メモリサイズが重要なのは、32ビットオペレーティングシステムで実行している場合で、2Gb(または場合によっては3Gb)のプロセスアドレススペースしか割り当てることができません。その場合は、リソースが不足しているため、大きなファイルのメモリマップや大量のスレッドを作成するためにヒープサイズを小さくするなどのトレードオフが必要になる場合があります。

しかし、64ビットマシンが遍在していることを考えると、Virtual Memory Sizeが完全に無関係な統計になるのはもうすぐだとは思いません。

居住者セットのサイズはいつ重要ですか?

常駐セットのサイズは、実際にRAMにある仮想メモリ空​​間の部分です。 RSSが物理メモリ全体のかなりの部分を占めるようになったら、心配するときが来るかもしれません。 RSSが大きくなり、すべての物理メモリを占有し、システムがスワップを開始した場合、心配するのはもう過去のことです。

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

ボトムライン

交換しない限り、さまざまなメモリ統計情報が何を伝えているかについて過度に心配しないでください。 RSSが増え続けることは、何らかのメモリリークを示している可能性があることに注意してください。

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

ディスク(データベース)へのアクセスは高価であり、メモリは安価です。一方を他方と交換できる場合は、そうします。

589
kdgregory

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でMALLOC_ARENA_MAXを検索するか、SOで詳細を参照してください。

他の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
35
Lari Hotari

Javaプロセスに割り当てられるメモリの量は、私が期待するものとほぼ同等です。組み込み/メモリが制限されたシステムでJavaを実行すると、同様の問題が発生しました。 any任意の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のデフォルトの動作は、double最大まで拡張するたびにヒープサイズのままであると考えています。 32Mのヒープから開始し、アプリが65Mに達した場合、ヒープは32M-> 64M-> 128Mになります。

また、これを試して、VMのヒープの成長に対する攻撃性を低くすることもできます。

-XX:MinHeapFreeRatio = 40展開を回避するためのGC後のヒープ解放の最小パーセンテージ。

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

9
James Schek

Sun JVMはHotSpotに大量のメモリを必要とし、共有メモリのランタイムライブラリにマップします。

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

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

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

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

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

Default parameters

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

MinHeap 10, MaxHeap 25

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

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

3
runholen

単なる考えですが、 a ulimit -v option の影響を確認できます。

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

3
VonC

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
Paul Tomblin

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

別の方法として、メモリフットプリントを小さくしてSun以外のJVMを試すこともできますが、ここではアドバイスできません。

0
Marko