33

過去1年間で、アプリケーションのJavaヒープ使用量を大幅に改善しました。これは66%の大幅な削減です。そのために、Javaヒープサイズ、CPU、Java非ヒープなどのさまざまなメトリックをSNMP経由で監視してきました。

最近、JVMによる実際のメモリ(RSS、常駐セット)の量を監視していて、少し驚いています。JVMによって消費される実際のメモリは、アプリケーションのヒープサイズ、非ヒープ、エデンスペース、スレッド数などとは完全に独立しているようです。

JavaSNMPで測定されたヒープサイズ Javaヒープ使用グラフhttp://lanai.dietpizza.ch/images/jvm-heap-used.png

KB単位の実メモリ。(例:1MBのKB= 1 GB) Javaヒープ使用グラフhttp://lanai.dietpizza.ch/images/jvm-rss.png

(ヒープグラフの3つのディップは、アプリケーションの更新/再起動に対応します。)

これは私にとって問題です。JVMが消費している余分なメモリはすべて、OSがファイルのキャッシュに使用できるメモリを「盗む」ためです。実際、RSS値が約2.5〜3 GBに達すると、アプリケーションからの応答時間が遅くなり、CPU使用率が高くなり始めます。ほとんどの場合、IO待機になります。スワップパーティションへのページングが始まると、これはすべて非常に望ましくありません。

だから、私の質問:

  • なぜこうなった?「ボンネットの下で」何が起こっているのですか?
  • JVMの実際のメモリ消費を抑えるにはどうすればよいですか?

残酷な詳細:

  • RHEL4 64ビット(Linux-2.6.9-78.0.5.ELsmp#1 SMP Wed Sep 24 ... 2008 x86_64 ... GNU / Linux)
  • Java 6(ビルド1.6.0_07-b06)
  • Tomcat 6
  • アプリケーション(オンデマンドHTTPビデオストリーミング)
    • java.nioファイルチャネルを介した高I/O
    • 数百から数千のスレッド
    • データベースの使用量が少ない
    • 春、休止状態

関連するJVMパラメーター:

-Xms128m  
-Xmx640m  
-XX:+UseConcMarkSweepGC  
-XX:+AlwaysActAsServerClassMachine  
-XX:+CMSIncrementalMode    

-XX:+PrintGCDetails 
-XX:+PrintGCTimeStamps  
-XX:+PrintGCApplicationStoppedTime  
-XX:+CMSLoopWarn  
-XX:+HeapDumpOnOutOfMemoryError 

RSSの測定方法:

ps x -o command,rss | grep java | grep latest | cut -b 17-

これはテキストファイルに送られ、定期的に監視システムのRRDデータベースに読み込まれます。psはキロバイトを出力することに注意してください。


問題と解決

最終的に正しいことが証明されたのはATorrasの答えでしたが、を使用して正しい診断パスに私を導いたのはkdgregorypmapでした。(両方の回答に投票してください!)これが起こっていたことです:

私が確かに知っていること:

  1. 私のアプリケーションは、3年以上前にアプリにコーディングしたJRobin1.4を使用してデータを記録および表示します。
  2. アプリケーションの最もビジーなインスタンスは現在作成します
    1. 起動から1時間以内に1000を超える新しいJRobinデータベースファイル(それぞれ約1.3MB)
    2. 起動後、毎日100以上
  3. 何か書き込むものがある場合、アプリはこれらのJRobinデータベースオブジェクトを15秒ごとに更新します。
  4. デフォルト構成のJRobin:
    1. java.nioベースのファイルアクセスバックエンドを使用します。このバックエンドMappedByteBuffersはファイル自体にマップされます。
    2. 5分ごとに1回、JRobinデーモンスレッドがMappedByteBuffer.force()すべてのJRobin基盤データベースMBBを呼び出します
  5. pmapリスト:
    1. 6500マッピング
    2. 5500は1.3MBのJRobinデータベースファイルで、これは最大7.1GBになります。

最後のポイントは私の「ユーレカ!」でした。一瞬。

私の是正措置:

  1. 明らかに優れている最新のJRobinLite1.5.2に更新することを検討してください
  2. JRobinデータベースに適切なリソース処理を実装します。現時点では、アプリケーションがデータベースを作成し、データベースがアクティブに使用されなくなった後は、データベースをダンプしません。
  3. MappedByteBuffer.force()定期的なタイマーではなく、データベース更新イベントに移動してみてください。問題は魔法のように消えますか?
  4. すぐに、JRobinバックエンドをjava.io実装に変更します(行の行を変更します)。これは遅くなりますが、おそらく問題ではありません。これは、この変更の直接の影響を示すグラフです。

JavaRSSメモリ使用グラフhttp://lanai.dietpizza.ch/images/stackoverflow-rss-problem-fixed.png

私が理解する時間があるかもしれないし、ないかもしれない質問:

  • JVM内で何が起こっているのMappedByteBuffer.force()ですか?何も変更されていない場合でも、ファイル全体が書き込まれますか?ファイルの一部?最初にロードしますか?
  • 常にRSSに一定量のMBBがありますか?(RSSは、割り当てられたMBBサイズの合計の約半分でした。偶然の一致ですか?そうではないと思います。)
  • MappedByteBuffer.force()定期的なタイマーではなく、データベース更新イベントに移動した場合、問題は魔法のように解消されますか?
  • RSSスロープがそれほど規則的だったのはなぜですか?これは、アプリケーションの負荷メトリックのいずれとも相関していません。
4

4 に答える 4

18

ただのアイデア:NIOバッファはJVMの外部に配置されます。

編集: 2016年のように、@ Lari Hotariのコメントを検討する価値があります[ヒープなどのサイズが安定している場合でも、Sun JVMがRSSメモリをこれまで以上に消費し続けるのはなぜですか?] 2009年にさかのぼると、RHEL4のglibc <2.10(〜2.3)

よろしく。

于 2009-10-23T12:19:14.890 に答える
14

RSSは、アクティブに使用されているページを表します。Javaの場合、RSSは主にヒープ内のライブオブジェクトであり、JVM内の内部データ構造です。使用するオブジェクトの数を減らすか、処理を減らす以外に、サイズを小さくするためにできることはあまりありません。

あなたの場合、それは問題ではないと思います。グラフには、テキストに書き込んだ3ギガではなく、3メガが消費されたことが示されているように見えます。これは非常に小さく、ページングを引き起こす可能性は低いです。

では、システムで他に何が起こっているのでしょうか。たくさんのTomcatサーバーがあり、それぞれが3MのRSSを消費している状況ですか?多くのGCフラグをスローしていますが、プロセスがほとんどの時間をGCに費やしていることを示していますか?同じマシンでデータベースを実行していますか?

コメントに応じて編集する

3M RSSサイズに関しては-ええ、Tomcatプロセスには小さすぎるように見えました(チェックボックスをオンにしたところ、89Mにしばらくアクティブになっていないものがあります)。ただし、必ずしもヒープサイズを超えるとは限りません。また、ヒープサイズのほぼ5倍になるとは限りません(-Xmx640を使用します)。最悪の場合、ヒープサイズとアプリごとのサイズである必要があります。絶え間ない。

それは私にあなたの番号を疑わせる原因になります。したがって、時間の経過に伴うグラフではなく、以下を実行してスナップショットを取得してください(7429を使用しているプロセスIDに置き換えてください)。

ps -p 7429 -o pcpu,cutime,cstime,cmin_flt,cmaj_flt,rss,size,vsize

(Stuによって編集して、上記のps情報の要求に対して結果をフォーマットできるようにします:)

[stu@server ~]$ ps -p 12720 -o pcpu,cutime,cstime,cmin_flt,cmaj_flt,rss,size,vsize
%CPU - - - -  RSS SZ  VSZ
28.8 - - - - 3262316 1333832 8725584

後世のためにこれらの数字を説明するために編集する

前述のように、RSSは常駐セットのサイズ、つまり物理メモリ内のページです。SZは、プロセスによって書き込み可能なページ数(コミットチャージ)を保持します。マンページでは、この値を「非常に大まかな」と説明しています。VSZは、プロセスの仮想メモリマップのサイズ(書き込み可能なページと共有ページ)を保持します。

通常、VSZはわずかに> SZであり、非常に>RSSです。この出力は、非常に異常な状況を示しています。

唯一の解決策がオブジェクトを減らすことである理由の詳細

RSSは、RAMに常駐するページ数(アクティブにアクセスされるページ)を表します。Javaでは、ガベージコレクターはオブジェクトグラフ全体を定期的にウォークします。このオブジェクトグラフがヒープスペースの大部分を占める場合、コレクターはヒープ内のすべてのページにアクセスし、それらのすべてのページがメモリ常駐になる必要があります。GCは、主要な各コレクションの後にヒープを圧縮するのに非常に優れているため、部分的なヒープを使用して実行している場合は、ほとんどのページがRAMにある必要はありません。

そして他のいくつかのオプション

数百から数千のスレッドがあるとおっしゃっていました。これらのスレッドのスタックもRSSに追加されますが、それほど多くはないはずです。スレッドの呼び出し深度が浅いと仮定すると(通常、アプリサーバーハンドラースレッドの場合)、それぞれにハーフメガのコミットチャージがある場合でも、各スレッドは1ページまたは2ページの物理メモリのみを消費する必要があります。

于 2009-10-23T12:01:00.287 に答える
3

なぜこうなった?「ボンネットの下で」何が起こっているのですか?

JVMは、ヒープだけでなく、より多くのメモリを使用します。たとえば、Javaメソッド、スレッドスタック、ネイティブハンドルは、JVM内部データ構造だけでなく、ヒープとは別のメモリに割り当てられます。

あなたの場合、トラブルの考えられる原因は次のとおりです:NIO(すでに言及されている)、JNI(すでに言及されている)、過度のスレッドの作成。

JNIについて、アプリケーションはJNIを使​​用していないと書いていましたが、...どのタイプのJDBCドライバーを使用していますか?タイプ2で、漏れている可能性がありますか?あなたが言ったように、データベースの使用率は低いですが、それは非常にありそうにありません。

過剰なスレッドの作成については、各スレッドが独自のスタックを取得しますが、これは非常に大きくなる可能性があります。スタックサイズは、実際にはVM、OS、およびアーキテクチャによって異なります。たとえば、JRockitの場合はLinux x64では256Kですが、SunのVMに関するSunのドキュメントで参照を見つけられませんでした。これは、スレッドメモリに直接影響します(スレッドメモリ=スレッドスタックサイズ*スレッド数)。また、大量のスレッドを作成して破棄した場合、メモリはおそらく再利用されません。

JVMの実際のメモリ消費を抑えるにはどうすればよいですか?

正直なところ、数百から数千のスレッドは私には巨大に思えます。とはいえ、本当に多くのスレッドが必要な場合は、オプションを使用してスレッドスタックサイズを構成でき-Xssます。これにより、メモリ消費が削減される場合があります。しかし、これで問題全体が解決するとは思いません。実メモリーグラフを見ると、どこかにリークがあると思いがちです。

于 2009-10-23T14:35:48.620 に答える
1

Javaの現在のガベージコレクタは、割り当てられたメモリを解放しないことでよく知られていますが、メモリはもう必要ありません。ただし、ヒープサイズが640MBに制限されているにもかかわらず、RSSサイズが3GBを超えるように増加するのは非常に奇妙です。アプリケーションでネイティブコードを使用していますか、それともTomcatのネイティブパフォーマンス最適化パックを有効にしていますか?その場合、もちろん、コードまたはTomcatでネイティブメモリリークが発生する可能性があります。

Sunは、Java 6u14で、新しい「ガベージファースト」ガベージコレクターを導入しました。これにより、メモリが不要になった場合にオペレーティングシステムにメモリを解放できます。まだ実験的なものとして分類されており、デフォルトでは有効になっていませんが、実行可能なオプションである場合は、最新のJava 6リリースにアップグレードし、コマンドライン引数「-XX:+UnlockExperimentalVMOptions-」を使用して新しいガベージコレクターを有効にしてみます。 XX:+UseG1GC」。それはあなたの問題を解決するかもしれません。

于 2009-10-23T12:19:57.783 に答える