40

ServletOutputStreamIBM Websphere Application Server v6 と Java 1.4 を使用しており、ユーザーがダウンロードできるように大きな CSV ファイルを に書き込もうとしています。現在、ファイルのサイズは 50 ~ 750 MB です。

小さいファイルはそれほど大きな問題を引き起こしていませんが、大きいファイルではヒープに書き込まれているように見え、それが OutOfMemory エラーを引き起こし、サーバー全体をダウンさせています。

これらのファイルは、HTTPS 経由で認証されたユーザーにのみ提供できます。そのため、Apache に貼り付けるのではなく、サーブレットを介して提供しています。

私が使用しているコードは次のとおりです(これに関するいくつかの綿毛が削除されました):

    resp.setHeader("Content-length", "" + fileLength);
    resp.setContentType("application/vnd.ms-excel");
    resp.setHeader("Content-Disposition","attachment; filename=\"export.csv\"");

    FileInputStream inputStream = null;

    try
    {
        inputStream = new FileInputStream(path);
        byte[] buffer = new byte[1024];
        int bytesRead = 0;

        do
        {
            bytesRead = inputStream.read(buffer, offset, buffer.length);
            resp.getOutputStream().write(buffer, 0, bytesRead);
        }
        while (bytesRead == buffer.length);

        resp.getOutputStream().flush();
    }
    finally
    {
        if(inputStream != null)
            inputStream.close();
    }

別のFileInputStreamファイルに書き込むか、書き込みを完全に削除するかのように問題を引き起こしているようには見えませんが、メモリ使用量は問題ではないようです。

私が考えているのresp.getOutputStream().writeは、データがクライアントに送信されるまでメモリに保存されているということです。そのため、ファイル全体が読み取られて保存されresp.getOutputStream()、メモリの問題やクラッシュが発生する可能性があります。

私はこれらのストリームをバッファリングしようとしましたが、 からjava.nioのチャネルを使用しようとしましたが、メモリの問題に少しの違いもないようです。OutputStreamまた、ループの反復ごとに 1 回とループの後にフラッシュしましたが、これは役に立ちませんでした。

4

10 に答える 10

46

平均的なまともなサーブレットコンテナ自体は、デフォルトでストリームを ~2KB ごとにフラッシュします。1 つの同じソースから連続してデータをストリーミングする場合、一定間隔flush()でを明示的に呼び出す必要はありません。たとえば、Tomcat (および Websphere!) では、これはHTTP コネクタの属性として構成可能です。OutputStreamHttpServletResponsebufferSize

平均的なまともなサーブレットコンテナは、コンテンツの長さが事前に不明な場合 (サーブレット API 仕様に従って!)、クライアントが HTTP 1.1 をサポートしている場合、データをチャンクでストリーミングするだけです。

問題の症状は、少なくともサーブレットコンテナがフラッシュする前にメモリ内のストリーム全体をバッファリングしていることを示しています。これは、コンテンツの長さヘッダーが設定されていない、および/またはサーブレットコンテナがチャンクエンコーディングをサポートしていない、および/またはクライアント側がチャンクエンコーディングをサポートしていない (つまり、HTTP 1.0 を使用している) ことを意味します。

どちらか一方を修正するには、コンテンツの長さを事前に設定するだけです。

response.setContentLengthLong(new File(path).length());

または、まだ Servlet 3.1 を使用していない場合:

response.setHeader("Content-Length", String.valueOf(new File(path).length()));
于 2010-06-16T12:09:02.957 に答える
1

この場合、flush()onが機能するかどうかもわかりませんが、応答をクライアントに送信する必要があります(少なくとも2.3サーブレット仕様に従って)。ServletOutputStreamServletResponse.flushBuffer()

ServletResponse.setBufferSize()有望に聞こえます。

于 2009-03-30T18:42:34.957 に答える
1
  1. ケビンのクラスはm_out、close()演算子でnullでない場合、フィールドを閉じる必要があります。リークしたくないのですが。

  2. ServletOutputStream.flush()オペレーターだけでなく、HttpServletResponse.flushBuffer()操作もバッファーをフラッシュする場合があります。ただし、これらの操作が効果を発揮するかどうか、またはhttpコンテンツの長さのサポートが干渉しているかどうかについては、実装固有の詳細のようです。content-lengthを指定することはHTTP1.0のオプションであるため、フラッシュする場合はストリームアウトする必要があることを忘れないでください。しかし、私はそれを見ていません

于 2010-06-21T16:17:06.150 に答える
1

while 条件は機能しません。使用する前に -1 を確認する必要があります。また、出力ストリームには一時変数を使用してください。読みやすく、安全に getOutputStream() を繰り返し呼び出すことができます。

OutputStream outStream = resp.getOutputStream();
while(true) {
    int bytesRead = inputStream.read(buffer);
    if (bytesRead < 0)
      break;
    outStream.write(buffer, 0, bytesRead);
}
inputStream.close();
out.close();
于 2011-06-24T10:17:38.317 に答える
1

flush出力ストリームで動作します。

本当に、バッファーは必ずしも完全に読み取られるとは限らないため (特にファイルの最後で (!))、3 引数形式の書き込みを使用する必要があることをコメントしたかったのです。また、サーバーを予期せず停止させたくない場合を除き、try/finally が適切です。

于 2009-03-26T11:44:53.837 に答える
1

それで、あなたのシナリオに従って、その while ループの外側ではなく、(反復ごとに)内側でフラッシュ(ing)するべきではありませんか?ただし、少し大きなバッファを使用して、それを試してみます。

于 2010-06-16T10:20:41.713 に答える
0

コードに無限ループがあります。

do
{
    bytesRead = inputStream.read(buffer, offset, buffer.length);
    resp.getOutputStream().write(buffer, 0, bytesRead);
}
while (bytesRead == buffer.length);

offsetはループ全体で同じ値を持つため、最初にoffset = 0の場合、反復ごとにそのまま残り、無限ループが発生し、OOM エラーが発生します。

于 2014-04-25T07:20:38.043 に答える
0

メモリの問題とは関係ありませんが、while ループは次のようになります。

while(bytesRead > 0);
于 2009-03-26T14:08:51.307 に答える
-1

IBM websphere アプリケーション サーバーは、デフォルトでサーブレットに非同期データ転送を使用します。つまり、応答をバッファリングします。大きなデータと OutOfMemory 例外に問題がある場合は、同期モードを使用するように WAS の設定を変更してみてください。

WebSphere Application Server WebContainer を同期モードに設定する

チャンクのロードとフラッシュにも注意する必要があります。大きなファイルからロードするためのサンプル。

ServletOutputStream os = response.getOutputStream();
FileInputStream fis = new FileInputStream(file);
            try {
                int buffSize = 1024;
                byte[] buffer = new byte[buffSize];
                int len;
                while ((len = fis.read(buffer)) != -1) {
                    os.write(buffer, 0, len);
                    os.flush();
                    response.flushBuffer();
                }
            } finally {
                os.close();
            }
于 2015-07-07T11:20:10.533 に答える