- PR -

ファイルPOST時でjava.lang.OutOfMemoryErrorが発生します。

1
投稿者投稿内容
マーク
会議室デビュー日: 2007/06/19
投稿数: 3
投稿日時: 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);
}

####################################################################################################

}
だっちょ
大ベテラン
会議室デビュー日: 2006/12/05
投稿数: 115
投稿日時: 2007-06-19 20:22
ちょっと見で気づいた点だけ、
out.write(buffer, 0, nRead);
の後でflushしてない。
終わったらcloseするのが普通だと思う。
あすか
ぬし
会議室デビュー日: 2006/07/12
投稿数: 309
投稿日時: 2007-06-20 09:03
私も気になった点を一つ
I/OにBufferedしていないのは意図的なもの?
マーク
会議室デビュー日: 2007/06/19
投稿数: 3
投稿日時: 2007-06-20 09:21
>だっちょさん
closeは私のバグでした。
修正いたしました。
ありがとうございます。

>あすかさん
確かにBufferedしていないのは適切ではないですね。
しかし・・・Bufferedしても相変わらずのエラーでした。

デバッグしていくと
out.write(buffer, 0, nRead);
で必ずおちます。

System.out.println(out.size());
を実行して落ちているサイズを見ると
どうもJVMのデフォルト64Mの半分程度で落ちている気がします。

ファイルをストリームに入れていくこと自体誤ったロジックなのでしょうか?
みなさんはどのように大きなファイルをストリームに入れ込んでいるのでしょう・・

ご教示お願いします_(._.)_
あしゅ
ぬし
会議室デビュー日: 2005/08/05
投稿数: 613
投稿日時: 2007-06-20 09:49
バッファ以上の容量のデータはメモリ上に維持していなさそうなので、
ファイル容量に依存する構造には見えませんでした。

PostStream(OutputStream o, String boundary)に渡ってくる
OutputStreamの実体って何ですか?こちらが問題なのでは?

POSTはContent-Lengthで送信データ長を指定する必要があるので、
メモリ上にごっそり蓄えてたりしませんか?

ソース上は他の書き込み前にflush()しているので無害ですが、
addProperty()でDataOutputStreamとOutputStreamWriterへの
出力が混在しているのはバグの原因になりやすそうです。

メソッド内ではどちらか一方だけを使うべきでしょう。
ヘッダ類のサイズは大したことないのでStringBufferなどで
組み立てた文字列をバイト変換して一回で書き込むのもよいです。

あとは、この用途でDataOutputStreamを使うのも微妙に思います。
DataOutputは簡易マーシャリング用の出力ストリームなので。
これを使っている目的はwriteBytes()だけですよね?

引用:

あすかさんの書き込み (2007-06-20 09:03) より:
私も気になった点を一つ
I/OにBufferedしていないのは意図的なもの?


addProperty()はバッファリングした方がよいですが、
addFile()はブロック単位の読み書きしているから不要です。
mio
ぬし
会議室デビュー日: 2005/08/25
投稿数: 734
お住まい・勤務地: 神奈川県
投稿日時: 2007-06-20 10:25
http://www.javaroad.jp/bbs/answer.jsp?q_id=20070619193925125

マルチポストするのなら、せめて相互にそう書いておきましょう。
マルチポスト自体、あまり良いことではないのですが。
解決できたら、両方にフィードバックを。
マーク
会議室デビュー日: 2007/06/19
投稿数: 3
投稿日時: 2007-06-20 10:54
>mio様

了解いたしました。
大変申し訳ありません。解決後両方にフィードバックいたします。
1

スキルアップ/キャリアアップ(JOB@IT)