10

バックグラウンド

ファイルを読み取り (使用しているサンプル ファイルのサイズは約 4 GB です)、ファイルに対して少量の処理を行い、それを Oracle データベースに書き出す Spring バッチ プログラムがあります。

私のプログラムは、1 つのスレッドを使用してファイルを読み取り、12 のワーカー スレッドを使用して処理とデータベースのプッシュを行います。

若い世代のメモリを大量に大量に消費しているため、プログラムが思ったよりも遅くなります。

設定

JDK 1.6.18
Spring バッチ 2.1.x
4 コア マシン、16 GB RAM 搭載

-Xmx12G 
-Xms12G 
-NewRatio=1 
-XX:+UseParallelGC
-XX:+UseParallelOldGC

問題

これらの JVM パラメータを使用すると、Tenured Generation で約 5.x GB のメモリ、Young Generation で約 5.X GB のメモリを取得できます。

この 1 つのファイルを処理する過程で、私の Tenured Generation は問題ありません。最大で 3 GB になるかもしれませんが、完全な GC を 1 回実行する必要はありません。

しかし、ヤングジェネレーションは何度もマックスに達します。それは 5 GB の範囲まで上がり、その後並列マイナー GC が発生し、Young Gen を 500MB の使用量までクリアします。マイナー GC は優れており、完全な GC よりも優れていますが、それでもプログラムの速度が大幅に低下します (データベース アクティビティが停止するため、若い世代のコレクションが発生すると、アプリがまだフリーズしていると確信しています)。プログラム時間の 5% 以上をマイナー GC のために凍結して費やしていますが、これは過剰に思えます。この 4 GB のファイルを処理する過程で、50 ~ 60 GB の若い世代のメモリを大量に消費したと言えます。

私のプログラムには明らかな欠陥は見当たりません。一般的な OO の原則に従い、クリーンな Java コードを作成しようとしています。理由もなくオブジェクトを作成しないようにしています。私はスレッド プールを使用しており、新しいオブジェクトを作成する代わりに、可能な限りオブジェクトを渡しています。アプリケーションのプロファイリングを開始しようとしていますが、過剰なメモリ チャーンを回避するための優れた一般的な経験則やアンチ パターンがあるかどうか疑問に思っていました。4GB のファイルを処理するには、50 ~ 60GB のメモリ チャーンが最適ですか? オブジェクト プーリングのような JDK 1.2 のトリックに戻す必要がありますか? (ただし、Brian Goetz はオブジェクト プーリングが馬鹿げている理由を含むプレゼンテーションを行いますが、もうそれを行う必要はありません。私は自分自身よりも彼を信頼しています .. :) )

4

7 に答える 7

9

気にしてはいけないことを最適化しようとして、時間と労力を費やしているような気がします。

プログラム時間の 5% 以上をマイナー GC のために凍結して費やしていますが、これは過剰に思えます。

それをひっくり返します。プログラム時間の 95% 弱を有益な作業に費やしています。別の言い方をすれば、ゼロ時間で実行するように GC を最適化できたとしても、得られる最高の改善は 5% を超えるものです。

アプリケーションに一時停止時間の影響を受ける厳しいタイミング要件がある場合は、低一時停止コレクターの使用を検討できます。(一時停止時間を短縮すると、全体的な GC オーバーヘッドが増加することに注意してください...) ただし、バッチ ジョブの場合、GC 一時停止時間は関係ありません。

おそらく最も重要なのは、バッチ ジョブ全体のウォール クロック時間です。そして、アプリケーション固有の作業に費やされる時間の (およそ) 95% は、プロファイリング/ターゲットを絞った最適化の取り組みに対してより多くの見返りを得る可能性が高い場所です。たとえば、データベースに送信する更新のバッチ処理について検討したことがありますか?


だから..私の総メモリの90%は、「oracle.sql.converter.toOracleStringWithReplacement」のchar []にあります

これは、データベースに送信するものを準備しているときに、メモリ使用量のほとんどが Oracle JDBC ドライバーで発生していることを示している傾向があります。それについてあなたはほとんどいません。避けられないオーバーヘッドとしてそれをチョークします。

于 2010-06-20T01:17:49.937 に答える
3

Java 6 にはわずかに異なる GC モデル (Eden、S0+S1、Old、Perm) があるため、「若い」世代と「慣れた」世代という用語を明確にすると非常に便利です。

さまざまなガベージ コレクション アルゴリズムを試してみましたか? 「UseConcMarkSweepGC」または「UseParNewGC」がどのように実行されたか。

また、使用可能なスペースを単純に増やすことは解決策ではないことを忘れないでください.gcの実行には時間がかかるため、サイズを通常の値に減らしてください;)

メモリリークがないことを確認していますか? 消費者-生産者-パターン-あなたが説明する-では、これらのジョブは非常に高速に処理されてから「破棄」されるため、データがOld Genにあることはめったにありませんか、それとも作業キューがいっぱいですか?

メモリ アナライザーを使用してプログラムを確実に観察する必要があります。

于 2010-06-19T21:01:39.200 に答える
2

メモリ プロファイラーを使用したセッションでは、この問題について多くのことが明らかになると思います。これにより、作成されたオブジェクトの数が分かりやすくなり、時には明らかになります。

生成される文字列の数にはいつも驚かされます。

それらを相互参照するドメインオブジェクトの場合も明らかになります。ソースからのオブジェクトよりも派生オブジェクトからのオブジェクトの方が突然 3 倍多い場合は、そこで何かが起こっています。

Netbeans には、それを構築した素晴らしいものがあります。以前は JProfiler を使用していました。日食を十分に長く叩けば、PPTPツールから同じ情報を取得できると思います。

于 2010-06-19T20:51:46.793 に答える
2

正確に何が起こっているかを確認するには、アプリケーションをプロファイリングする必要があります。また、推奨されているように、最初に JVMのエルゴノミクス機能を使用してみます。

2.人間工学

ここでエルゴノミクスと呼ばれる機能は、J2SE 5.0 で導入されました。人間工学の目標は、コマンド ライン オプションをほとんどまたはまったく調整せずに、

  • ガベージコレクター、
  • ヒープサイズ、
  • およびランタイム コンパイラ

固定のデフォルトを使用する代わりに、JVM の起動時に。この選択は、アプリケーションが実行されるマシンのクラスが、アプリケーションの特性 (つまり、大きなアプリケーションが大きなマシンで実行される) に関するヒントであると想定しています。これらの選択に加えて、ガベージ コレクションを調整する簡単な方法があります。並列コレクターを使用すると、ユーザーはアプリケーションの最大一時停止時間と必要なスループットの目標を指定できます。これは、良好なパフォーマンスに必要なヒープのサイズを指定するのとは対照的です。これは、大きなヒープを使用する大規模なアプリケーションのパフォーマンスを特に向上させることを目的としています。より一般的なエルゴノミクスについては、「5.0 Java 仮想マシンのエルゴノミクス」というタイトルのドキュメントで説明されています。このドキュメントで説明されているより詳細なコントロールを使用する前に、後者のドキュメントで提示されているエルゴノミクスを試すことをお勧めします

このドキュメントには、並列コレクターの適応サイズ ポリシーの一部として提供されるエルゴノミクス機能が含まれています。これには、ガベージ コレクションのパフォーマンスの目標を指定するオプションと、そのパフォーマンスを微調整するための追加オプションが含まれます。

Java SE 6 HotSpot[tm] 仮想マシン ガベージ コレクション チューニングガイドのエルゴノミクスに関する詳細なセクションを参照してください。

于 2010-06-19T21:57:42.447 に答える
1

メモリー制限が高いので、処理を行う前にファイルを完全にメモリーに読み込む必要があると思います。代わりにjava.io.RandomAccessFileの使用を検討できますか?

于 2010-06-19T21:25:06.490 に答える
1

私の意見では、小さなガベージ コレクションが高速に維持されるように、若い世代は古い世代と同じ大きさであってはなりません。

同じ値を表すオブジェクトが多数ありますか? その場合は、単純な を使用してこれらの重複オブジェクトをマージしますHashMap

public class MemorySavingUtils {

    ConcurrentHashMap<String, String> knownStrings = new ConcurrentHashMap<String, String>();

    public String unique(String s) {
        return knownStrings.putIfAbsent(s, s);
    }

    public void clear() {
        knownStrings.clear();
    }
}

Sun Hotspot コンパイラを使用すると、String.intern()多数の文字列に対してネイティブが非常に遅くなります。そのため、独自の String interner を作成することをお勧めします。

この方法を使用すると、古い世代の文字列が再利用され、新しい世代の文字列はすばやくガベージ コレクションできます。

于 2010-06-19T20:55:41.643 に答える
1

ファイルから行を読み取り、文字列として保存してリストに入れます。リストにこれらの文字列が 1000 個ある場合は、それをキューに入れ、ワーカー スレッドが読み取れるようにします。ワーカー スレッドがドメイン オブジェクトを作成し、文字列から一連の値を剥がしてフィールド (int、long、java.util.Date、または String) を設定し、ドメイン オブジェクトをデフォルトの Spring バッチ jdbc ライターに渡します。

それがあなたのプログラムなら、なぜ 256MB のような小さいメモリサイズを設定しないのですか?

于 2010-06-19T21:20:37.853 に答える