2

私は非常に基本的なものに苦労してきました。問題は、長いポーリングに Jetty の継続を使用することに関連しています。

簡単にするために、アプリケーション固有のコードをすべて削除し、単純な継続関連のコードを残しました。

以下に、サーブレットの doPost メソッドを貼り付けます。専門家のガイダンスが必要な重要な質問は、

  • 以下のコード ブロックをそのまま実行し、約 200 バイトのポスト ボディを運ぶポスト リクエストを発行すると、500 の長いポーリング接続のメモリ量は約 20 MB になります。
  • 強調表示されたブロックを「メモリ フットプリントを減らす :: コメント ブロックを下に」とコメントすると、メモリ フット プリントは 7 MB になります。

どちらの場合も、システムが安定するのを待ち、GC を複数回呼び出してから、jConsole を介してメモリを読み取ります。正確ではありませんが、違いは非常に大きく説明可能であるため、100バイト程度の精度は問題になりません。

サーバーが 100K 接続を保持する必要があることを考えると、私の問題は爆発します。そしてここで、この説明のつかないサイズの増加は、最終的に GB 近くの余分なヒープを使用することにつながります。

(ストリームから読み取られたものでさえ doPost メソッドの範囲外に保存されていない場合、この余分なヒープ使用の原因は何ですか。しかし、それでもヒープに追加されます....何が欠けていますか?)

   @Override
   protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

    Continuation cc = ContinuationSupport.getContinuation(req);

    //if continuation is resumed, then send an answer back with 
    //hardcoded answer
    if (cc.isResumed()) {
        String myJson = "{\"1\",\"2\"}";
        res.setContentType("application/json");
        res.setContentLength(myJson.length());
        PrintWriter writer = res.getWriter();
        writer.write(myJson);
        writer.close();
    } 
    // if it is the first call to doPost ( not reentrant call )
    else if (cc.isInitial()) {          

        //START :: decrease memory footprint :: comment this block :: START

        // store the json from the request body in a string
        StringBuffer jsonString = new StringBuffer();
        String line = null;                      
        BufferedReader bufferedReader = req.getReader();
        while ((line = bufferedReader.readLine()) != null) {
            jsonString.append(line);
        }  

        //here jsonString was parsed and some values extracted
        //though that code is removed for the sake of this publish
        // as problem exists irrespective...of any processing

        line = null;            
        bufferedReader.close();
        bufferedReader = null;
        jsonString = null;

        // END :: decrease memory footprint :: comment this block :: END

        cc.setTimeout(150000);        

        cc.suspend();
    }
}
4

1 に答える 1

1

この余分なヒープ使用の原因は何ですか...

この行を見てください:

BufferedReader bufferedReader = req.getReader();

実際には新しい BufferedReader を作成していないことに注意してください。を呼び出すとgetBufferedReader、Jetty は、バイト バッファをラップするカスタム InputStream 実装をラップする InputStreamReader をラップする BufferedReader を作成します。メッセージ全体を読み取るコードを実行すると、メッセージ本文の内容全体を格納するリクエスト オブジェクト内に大きなバイト バッファが作成されると確信しています。さらに、リクエスト オブジェクトはリーダーへの参照を維持します。

呼び出した関数の先頭で:

Continuation cc = ContinuationSupport.getContinuation(req);

あなたの継続は、すべてのデータを保存しているリクエストを保持していると思います。discontinueしたがって、データを読み取るという単純な行為は、続行するまで保持されるメモリを割り当てることです。

実験として試してみることができます。コードを次のように変更します。

BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(req.getInputStream()));

このようにして、Jetty は独自のリーダーを割り当てません。繰り返しますが、リクエストオブジェクトの残りの部分と比較して、実際にリーダーにどれだけのデータが保存されているかはわかりませんが、少しは役立つかもしれません。

[アップデート]

もう 1 つの方法は、問題を回避することです。それが私がしたことです (ただし、継続ではなくサーブレット 3.0 を使用していました)。私はリソースを持っていました - それを呼び出して/transferデータを POST し、 を使用しAsyncContextて応答を待ちます。URL が異なる 2 つのリクエストに変更しました -/push/pull. クライアントからサーバーに送信する必要があるコンテンツがあるときはいつでも、/pushリクエストに入れられ、AsyncContext. したがって、リクエスト内のすべてのストレージはすぐに解放されます。次に、応答を待つために、メッセージ本文なしで 2 番目の GET 要求を送信しました。確かに - リクエストはしばらく滞っていますが、誰が気にしますか - コンテンツはありません。

問題を再考し、タスクを分割して (複数の要求で) 実行できるかどうか、または単一の要求ですべてを処理する必要があるかどうかを判断する必要がある場合があります。

于 2012-12-02T00:30:40.837 に答える