IBM Websphere Application Server v6とJava 1.4を使用しており、ユーザーがダウンロードするためにServletOutputStream
に大きなCSVファイルを書き込もうとしています。ファイルの範囲は50〜750MBです現時点では。
小さいファイルが問題を引き起こしているわけではありませんが、大きいファイルではヒープに書き込まれているように見え、それが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回、ループの後に1回フラッシュしましたが、助けにはなりませんでした。
平均的なまともなservletcontainer自体は、デフォルトで〜2KBごとにストリームをフラッシュします。まったく同じソースからデータを連続してストリーミングする場合、OutputStream
のHttpServletResponse
で間隔を置いて明示的にflush()
を呼び出す必要はありません。たとえば、Tomcat(およびWebsphere!)では、これはHTTPコネクターのbufferSize
属性として構成可能です。
コンテンツの長さが事前に不明な場合( Servlet API仕様 !に従って)、クライアントがHTTP 1.1をサポートしている場合、平均的なまともなサーブレットコンテナは chunks でデータをストリーミングします。
問題の症状は、少なくともサーブレットコンテナがフラッシュ前にストリーム全体をメモリにバッファリングしていることを示しています。これは、コンテンツ長ヘッダーが設定されていない、および/またはサーブレットコンテナがチャンクエンコーディングをサポートしていない、および/またはクライアント側がチャンクエンコーディングをサポートしていない(つまり、HTTP 1.0を使用している)ことを意味します。
どちらかを修正するには、事前にコンテンツの長さを設定するだけです。
response.setHeader("Content-Length", String.valueOf(new File(path).length()));
Kevinのクラスは、close()演算子でnullでない場合、_m_out
_フィールドを閉じる必要があります。物事を漏らしたくないのですか?
ServletOutputStream.flush()
演算子と同様に、HttpServletResponse.flushBuffer()
オペレーションもバッファをフラッシュします。ただし、これらの操作に効果があるかどうか、またはhttpコンテンツの長さのサポートが妨げられているかどうかは、実装固有の詳細のようです。 content-lengthの指定はHTTP 1.0のオプションであるため、物事をフラッシュする場合は物事がただストリームアウトするはずです。しかし、私はそれを見ません
したがって、シナリオに従って、whileループの外側ではなく、(繰り返しごとに)whileループの内側にフラッシュするべきではありませんか?ただし、少し大きいバッファーで試してみます。
flush
は出力ストリームで機能しますか。
バッファは必ずしも完全に読み取られるとは限らないため、3引数形式のwriteを使用する必要があることをコメントしたかったのです(特にfile(!)の最後)。また、サーバーを予期せず停止させない限り、try/finallyが適切です。
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();
この場合、ServletOutputStream
でflush()
が機能するかどうかもわかりませんが、ServletResponse.flushBuffer()
はクライアントに応答を送信する必要があります(少なくとも2.3サーブレット仕様ごと)。
ServletResponse.setBufferSize()
も有望です。
出力ストリームをラップするクラスを使用して、他のコンテキストで再利用できるようにしました。ブラウザーへのデータの取得を高速化する上で、私にとってはうまく機能しましたが、メモリーへの影響は見ていません。 (時代遅れのm_変数の命名はご容赦ください)
import Java.io.IOException;
import Java.io.OutputStream;
public class AutoFlushOutputStream extends OutputStream {
protected long m_count = 0;
protected long m_limit = 4096;
protected OutputStream m_out;
public AutoFlushOutputStream(OutputStream out) {
m_out = out;
}
public AutoFlushOutputStream(OutputStream out, long limit) {
m_out = out;
m_limit = limit;
}
public void write(int b) throws IOException {
if (m_out != null) {
m_out.write(b);
m_count++;
if (m_limit > 0 && m_count >= m_limit) {
m_out.flush();
m_count = 0;
}
}
}
}
コードには無限ループがあります。
do
{
bytesRead = inputStream.read(buffer, offset, buffer.length);
resp.getOutputStream().write(buffer, 0, bytesRead);
}
while (bytesRead == buffer.length);
offsetはループ全体で同じ値を持っているため、最初にoffset =の場合、無限ループを引き起こし、OOMエラーにつながるすべての反復でそうなります。
メモリの問題とは無関係に、whileループは次のようになります。
while(bytesRead > 0);