- - PR -
ファイルPOST時でjava.lang.OutOfMemoryErrorが発生します。
1
投稿者 | 投稿内容 | ||||
---|---|---|---|---|---|
|
投稿日時: 2007-06-19 19:47
大きな容量のファイルをPOSTする際にjava.lang.OutOfMemoryErrorが発生して困っています。
httpclientは自前で作成しているのですが、 どこに問題があるかわからないのです。 色々調べるとヒープサイズの増加で対応自体はできると思うのですが、 抜本的な解決とはとても考えられないのです。 30M程度のファイルであれば問題なくPOSTできるのですが、 40Mを超えるファイルをのせてPOSTするとダメなのです。 フリーメモリ容量をチェックしても大丈夫そうですし・・・ gcを実行しても特に変わりはありません。 以下が問題のPOST部のソースになるのですが、 どこか問題があるのでしょうか? バッファとって順番に実行はしてるのですが・・・ /* PostStream.java */ import java.io.*; import java.net.*; public class PostStream { protected final DataOutputStream out; protected final Writer wout; private final String boundary; private static final String SEPARATOR = "--"; private static final byte[] CRLF = { 0x0d, 0x0a }; private boolean needDelimeter = false; /** * 「URLエンコード」モードのデータストリームを作成します。<br> * addProperty(java.lang.String, java.lang.String) で追加されるデータは適切に URL エンコードされます。<br> * close() すると、Content-Type を "application/x-www-urlencoded" として POST リクエストを実行します。 * * @param o URLConnection.getOutputStream() で返される {@link URLConnection#getOutputStream} */ public PostStream (OutputStream o) { this(o, null); } /** * 「マルチパート」モードのデータストリームを作成します. addProperty(java.lang.String, java.lang.String) や addFile(java.io.File, java.lang.String, java.lang.String) で追加されるデータはマルチパート MIME 形式にフォーマットされます。 * close() すると、Content-Type を "multipart/form-data" として POST リクエストを実行します。 * * @param o {@link URLConnection#getOutputStream} で返される OutputStream * @param boundary マルチパート MIME の区切文字列 */ public PostStream (OutputStream o, String boundary) { if ( o == null ) throw new NullPointerException("o is null."); out = new DataOutputStream(o); Writer tmp_wout = null; try { tmp_wout = new OutputStreamWriter(out, "UTF-8"); // tmp_wout = new OutputStreamWriter(out, "Shift_JIS"); } catch (UnsupportedEncodingException e) { // ignore it } finally { wout = tmp_wout; } this.boundary = boundary; } private static String encode (String s) { String encoded = null; try { encoded = URLEncoder.encode(s, "UTF-8"); // encoded = URLEncoder.encode(s, "Shift_JIS"); } catch ( UnsupportedEncodingException e ) { // ignore it } return encoded; } /** * パラメータをストリームに追加します。 * * @param name パラメータ名 * @param value パラメータの値 * @throws IOException 書き込み時にエラーが発生した */ public void addProperty (String name, String value) throws IOException { if ( boundary == null ) { if ( needDelimeter ) wout.write('&'); String encoded = encode(name) + "=" + encode(value); wout.write(encoded); wout.flush(); needDelimeter = true; return; } out.writeBytes(SEPARATOR); out.writeBytes(boundary); out.write(CRLF); wout.write("Content-Disposition: form-data; name=\"" + name + "\""); wout.flush(); out.write(CRLF); out.write(CRLF); wout.write(value); wout.flush(); out.write(CRLF); } /** * パラメータをストリームに追加します。 * * @param name パラメータ名 * @param value パラメータの値 * @throws IOException 通信エラーが発生 */ public void addProperty (String name, int value) throws IOException { addProperty(name, Integer.toString(value)); } ####################ここからが問題の部分です!######################################### /** * ファイルの内容をストリームに追加します。<br> * 「マルチパート」モードのストリームでしか使用できません。 * * @param file 追加するファイル * @param name パラメータ名 * @param mimeType このファイルの MIME タイプ * @throws IOException 通信エラーが発生 * @see #writeFileEntry */ public void addFile (File file, String name, String mimeType) throws IOException { if ( boundary == null ) throw new IllegalStateException("could not add a file for this stream."); out.writeBytes(SEPARATOR); out.writeBytes(boundary); out.write(CRLF); wout.write("Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + file.getName() + "\""); wout.flush(); out.write(CRLF); wout.write("Content-Type: "); wout.flush(); out.writeBytes(mimeType); out.write(CRLF); out.write(CRLF); // put file contents FileInputStream reader = new FileInputStream(file); //reader = new BufferedInputStream(reader); final int BUFSIZE = 4096; byte[] buffer = new byte[BUFSIZE]; while ( true ) { int nRead = reader.read(buffer); if ( nRead == -1 ) break; out.write(buffer, 0, nRead); } out.write(CRLF); } #################################################################################################### } | ||||
|
投稿日時: 2007-06-19 20:22
ちょっと見で気づいた点だけ、
out.write(buffer, 0, nRead); の後でflushしてない。 終わったらcloseするのが普通だと思う。 | ||||
|
投稿日時: 2007-06-20 09:03
私も気になった点を一つ
I/OにBufferedしていないのは意図的なもの? | ||||
|
投稿日時: 2007-06-20 09:21
>だっちょさん
closeは私のバグでした。 修正いたしました。 ありがとうございます。 >あすかさん 確かにBufferedしていないのは適切ではないですね。 しかし・・・Bufferedしても相変わらずのエラーでした。 デバッグしていくと out.write(buffer, 0, nRead); で必ずおちます。 System.out.println(out.size()); を実行して落ちているサイズを見ると どうもJVMのデフォルト64Mの半分程度で落ちている気がします。 ファイルをストリームに入れていくこと自体誤ったロジックなのでしょうか? みなさんはどのように大きなファイルをストリームに入れ込んでいるのでしょう・・ ご教示お願いします_(._.)_ | ||||
|
投稿日時: 2007-06-20 09:49
バッファ以上の容量のデータはメモリ上に維持していなさそうなので、
ファイル容量に依存する構造には見えませんでした。 PostStream(OutputStream o, String boundary)に渡ってくる OutputStreamの実体って何ですか?こちらが問題なのでは? POSTはContent-Lengthで送信データ長を指定する必要があるので、 メモリ上にごっそり蓄えてたりしませんか? ソース上は他の書き込み前にflush()しているので無害ですが、 addProperty()でDataOutputStreamとOutputStreamWriterへの 出力が混在しているのはバグの原因になりやすそうです。 メソッド内ではどちらか一方だけを使うべきでしょう。 ヘッダ類のサイズは大したことないのでStringBufferなどで 組み立てた文字列をバイト変換して一回で書き込むのもよいです。 あとは、この用途でDataOutputStreamを使うのも微妙に思います。 DataOutputは簡易マーシャリング用の出力ストリームなので。 これを使っている目的はwriteBytes()だけですよね?
addProperty()はバッファリングした方がよいですが、 addFile()はブロック単位の読み書きしているから不要です。 | ||||
|
投稿日時: 2007-06-20 10:25
http://www.javaroad.jp/bbs/answer.jsp?q_id=20070619193925125
マルチポストするのなら、せめて相互にそう書いておきましょう。 マルチポスト自体、あまり良いことではないのですが。 解決できたら、両方にフィードバックを。 | ||||
|
投稿日時: 2007-06-20 10:54
>mio様
了解いたしました。 大変申し訳ありません。解決後両方にフィードバックいたします。 |
1