14

非常に高密度のトラフィックを大量に処理するJavaサーバーに取り組んでいます。サーバーはクライアント(多くの場合、数メガバイト)からのパケットを受け入れ、他のクライアントに転送します。サーバーは、着信/発信パケットを明示的に保存することはありません。それでも、サーバーは継続的にOutOfMemoryException例外に遭遇します。

System.gc()サーバーのメッセージパッシングコンポーネントに、メモリが解放されることを期待して追加しました。さらに、JVMのヒープサイズをギガバイトに設定しました。私はまだ同じくらい多くの例外を受け取っています。

だから私の質問はこれです:メガバイトのメッセージが無期限にキューに入れられていないことをどのように確認できますか(必要ではないにもかかわらず)?これらのオブジェクトで「削除」を呼び出して、ヒープスペースを使用していないことを保証する方法はありますか?

        try
        {
           while (true)
            {
               int r = generator.nextInt(100);//generate a random number between 0 and 100
                Object o =readFromServer.readObject();
                sum++;
                // if the random number is larger than the drop rate, send the object to client, else
                //it will be dropped
                if (r > dropRate)
                {
                    writeToClient.writeObject(o);
                    writeToClient.flush();
                    numOfSend++;
                    System.out.printf("No. %d send\n",sum);
                }//if

            }//while
        }//try
4

14 に答える 14

19

オブジェクト ストリームは、そこから書き込まれた/読み取られたすべてのオブジェクトへの参照を保持します。これは、シリアライゼーション プロトコルが、ストリーム内で以前に出現したオブジェクトへの逆参照を許可しているためです。この設計を引き続き使用できる可能性がありますが、writeObject/readObject の代わりに writeUnshared/readUnshared を使用してください。確かではありませんが、これによりストリームがオブジェクトへの参照を保持できなくなると思います。

Cowan が言うように、このreset()方法もここで有効です。最も安全な方法は、 sに書き込むときにwriteUnsharedすぐに続けて使用することです。reset()ObjectOutputStream

于 2010-02-01T16:32:13.187 に答える
11

JVM が のエッジにある場合OutOfMemoryError、GCが実行されます。

したがって、System.gc()事前に電話しても問題は解決しません。問題は別の場所で修正する必要があります。基本的に次の 2 つの方法があります。

  1. メモリ効率の高いコードを記述し、コード内のメモリ リークを修正します。
  2. JVM にメモリを追加します。

Java プロファイラーを使用すると、メモリ使用量と潜在的なメモリ リークに関する多くの情報が得られる場合があります。

更新: この問題の原因となっているコードに関する詳細情報を編集したとおり、このトピックの Geoff Reedy の回答を参照してください。代わりにObjectInputStream#readUnshared()andを使用することをお勧めします。ObjectOutputStream#writeUnshared()(リンクされた) Javadocs もそれをかなりよく説明しています。

于 2010-02-01T16:05:09.883 に答える
4

System.gc() は、Java 仮想マシンへの推奨事項にすぎません。これを呼び出すと、JVM がガベージ コレクションを実行する場合と実行しない場合があります。

OutOfMemoryException は、2 つの原因で発生する可能性があります。オブジェクトへの(不要な)参照を保持しているか、多くのパケットを受け入れています。

最初のケースは、プロファイラーによって分析できます。ここでは、まだ生きている参照の数を見つけようとします。メモリ リークの良い兆候は、サーバーのメモリ消費量の増加です。リクエストを追加するたびに Java プロセスが少し大きくなる場合は、どこかに参照を保持している可能性があります (jconsole が良い出発点かもしれません)。

処理できる以上のデータを受け入れる場合は、他の要求が完了するまで追加の要求をブロックする必要があります。

于 2010-02-01T16:09:14.260 に答える
3

コードを見るとObjectInput/OutputStream、パケットが到着または送信されるたびにインスタンスが新しく作成されていますか?その場合、インスタンスは適切に閉じられていますか?そうでない場合は、reset()読み取り/書き込みのたびに電話をかけますか?オブジェクトストリームクラスは、(参照されるたびに同じオブジェクトを再送信しないようにするために)表示したすべてのオブジェクトへの参照を保持し、ガベージコレクションが行われないようにします。私は約10年前にその正確な問題を抱えていました-実際、メモリリークを診断するためにプロファイラーを使用しなければならなかったのは初めてです...

于 2010-02-01T17:03:40.337 に答える
3

明示的なガベージ コレクションを呼び出すことはできません。しかし、これはここでの問題ではありません。これらのメッセージへの参照を保存している可能性があります。それらが処理された場所を追跡し、それらが使用された後にそれらへの参照を保持するオブジェクトがないことを確認してください。

ベスト プラクティスとは何かをよりよく理解するには、Effective Java の第 2 章を読んでください。「オブジェクトの作成と破棄」についてです。

于 2010-02-01T16:04:52.690 に答える
2

明示的に削除を強制することはできませんが、メモリ内に直接参照を 1 つだけ保持し、参照オブジェクトを使用してガベージ コレクション可能な参照を保持することで、メッセージへの参照が保持されないようにすることができます。

メッセージを処理するための (小さく制限されたサイズの) キューを使用してから、最初のキューにフィードするセカンダリ SoftReference キューを使用するのはどうですか? このようにして、処理が進行することを保証しますが、メッセージが大きすぎる場合にメモリ不足エラーが発生しないことも保証します (その場合、参照キューはダンプされます)。

于 2010-02-01T16:05:24.483 に答える
2

Java でガベージ コレクションを調整することはできますが、強制することはできません。

于 2010-02-01T16:06:07.247 に答える
1

OutOfMemory 例外が発生している場合、何かがこれらのオブジェクトへの参照をまだ保持していることは明らかです。jhatなどのツールを使用して、これらの参照が残っている場所を見つけることができます。

于 2010-02-01T16:05:53.823 に答える
1

必要以上に長く物体を保持しているかどうかを確認する必要があります。最初のステップは、ケースのプロファイラーを取得し、ヒープを調べて、オブジェクトが収集されていない理由を確認することです。

JVM に 1GB を指定しましたが、多くのオブジェクトが非常に迅速に作成されて古い世代になり、オブジェクトがすぐに削除されない場合は、若い世代が小さすぎる可能性があります。

GC チューニングに関する役立つ情報: http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html

于 2010-02-01T16:06:34.673 に答える
1

サーバーは、クライアントからのパケット (多くの場合、数メガバイト) を受け取り、それらを他のクライアントに転送します。

あなたのコードはおそらく「パケット」を転送する前に完全に受け取ります。これは、完全に転送されるまですべてのパケットを完全に保存するのに十分なメモリが必要であることを意味し、それらのパケットが「何メガバイトも大きい」場合、実際に多くのメモリが必要であることを意味します。また、不必要な待ち時間が発生します。

メモリ リークが発生している可能性もありますが、上記が当てはまる場合は、この「ストア アンド フォワード」設計が最大の問題です。アプリを再設計して、パケットを完全に受信せずにクライアントに直接ストリーミングする場合、つまり、一度にパッケージのごく一部のみを読み取り、それをすぐにクライアントに送信する場合、おそらくメモリ使用量を 95% 削減できます。ストア アンド フォワードを行う場合とまったく同じようにクライアントに表示される方法でこれを行うことは難しくありません。

于 2010-02-01T16:19:43.467 に答える
0

サーバーが停止する前に少なくとも数分間実行する場合は、VisualVMで実行してみてください。少なくとも、ヒープの成長速度と、ヒープ内にあるオブジェクトの種類について、より良い理解が得られるかもしれません。

于 2010-02-01T17:02:40.637 に答える
0

他の人がここに投稿しているように、System.gcを手動でトリガーすることは良い答えではありません。実行が保証されているわけではなく、完全なgcがトリガーされ、実行中にサーバーが長時間ハングする可能性があります(サーバーに1 GBのRAMを割り当てている場合は、1秒以上かかります。数分で確認できます。大規模なシステムでは長い一時停止)。あなたは確かに役立つあなたのgcを調整することができますが、問題を完全に修正することはできません。

あるストリームからオブジェクトを読み取り、次にそれらを別のストリームに書き出す場合、オブジェクト全体をメモリに保持しているポイントがあります。あなたが言うように、これらのオブジェクトが大きい場合、それはあなたの問題である可能性があります。オブジェクト全体を明示的に保持せずに、1つのストリームからバイトを読み取り、別のストリームに書き込むようにIOを書き直してみてください(ただし、オブジェクトを検証/妥当性確認する必要がある場合、これがオブジェクトのシリアル化/逆シリアル化でどのように機能するかはわかりません) )。

于 2010-02-01T16:25:04.720 に答える
0

以前のすべての応答に追加するだけです。System.gc()は、ガベージコレクションを開始するためのJVMへのコマンドではありません。これは穏やかな方向であり、何が起こるかを保証するものではありません。JVM仕様では、gc呼び出しで何を行う必要があるかをベンダーに任せています。ベンダーは何もしないことを選択することさえできます!

于 2010-02-01T16:26:04.500 に答える
0

送信する前に、受信したパケット全体が明示的に必要であると述べていますか? まあ、それはすべてをメモリに保存する必要があるという意味ではありませんね。受信したパケットを外部ストア (SSD でさえ遅すぎる場合は RAM ディスクまたは DB) に保存し、それらを完全にメモリにロードすることなく受信者に直接パイプすることは、実現可能なアーキテクチャの変更ですか?

于 2010-02-01T16:33:50.597 に答える