2011-10-26 6 views
0

私はJava 6にHttpServerを埋め込みました。これは、クライアントが大きなテキストファイルをダウンロードできるようにハンドルがあります。問題は、サーバーに10以上のクライアントが同時に接続されている場合、メモリ不足例外が発生することです。私は問題がHttpサーバーの周りにあることを前提としています。 )(HttpServer上のjava.lang.OutOfMemoryError大きなデータをダウンロード中

java.lang.OutOfMemoryError: Java heap space 
at java.nio.HeapByteBuffer.<init>(Unknown Source) 
at java.nio.ByteBuffer.allocate(Unknown Source) 
at sun.net.httpserver.Request$WriteStream.write(Unknown Source) 
at sun.net.httpserver.FixedLengthOutputStream.write(Unknown Source) 
at java.io.FilterOutputStream.write(Unknown Source) 
at sun.net.httpserver.PlaceholderOutputStream.write(Unknown Source) 
at com.shunra.javadestination.webservices.DownloadFileHandler.handle(Unknown Source) 
at com.sun.net.httpserver.Filter$Chain.doFilter(Unknown Source) 
at sun.net.httpserver.AuthFilter.doFilter(Unknown Source) 
at com.sun.net.httpserver.Filter$Chain.doFilter(Unknown Source) 
at sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(Unknown Source) 
at com.sun.net.httpserver.Filter$Chain.doFilter(Unknown Source) 
at sun.net.httpserver.ServerImpl$Exchange.run(Unknown Source) 
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source) 
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) 
at java.lang.Thread.run(Unknown Source) 
Exception in thread "pool-1-thread-24" java.lang.OutOfMemoryError: 

GetBytesメソッドに関する提案例外を変更しません:

HttpServer m_server = HttpServer.create(new InetSocketAddress(8080), 0); 
    m_server.createContext("/DownloadFile", new DownloadFileHandler()); 

    public class DownloadFileHandler implements HttpHandler { 

     private static byte[] myFile = new String("....................").getBytes(); //string about 8M 

     @Override 
     public void handle(HttpExchange exchange) throws IOException { 
       exchange.sendResponseHeaders(HTTP_OK, myFile .length);     OutputStream responseBody = exchange.getResponseBody(); 
       responseBody.write(myFile); 
       responseBody.close(); 
     } 
    } 

今私が得る例外があります。私は毎回それを作成するのではなく、バイト[]への静的な参照を保持しようとしました。そして、私はまだ同じ例外を取得します。

+0

してください、これは一度に2つの問題かもしれないようにあなたのコードの周りにいくつかの「コードの引用符は、」.. – kgautron

+0

@Sophieはこれが聞こえます。 Javaの問題、およびスレッドの処理に関する問題が含まれます。どのように多くの接続がHTTPサーバーに組み込まれていますか?どのようなHTTPサーバーを使用していますか? – bakoyaro

+0

@Sophieまた、ソースをコンパイルするときにdebug = trueを設定すると、スタックトレースに行番号が表示されます。 – bakoyaro

答えて

7

は、大きなファイルのためにそれをしないでください:これは非効率的であり、あなたが全体のファイルデータを格納するためにヒープ領域を必要とする

byte[] bytesToSend = myFile.getBytes(); 

。最初にファイルを完全に読み込んだ後に完全に書き込むと、たくさんのヒープスペースが無駄になります。

代わりに、特定のサイズのチャンクでファイルデータをファイルから直接レスポンスに読み書きします。あなた自身でコードを書いたり、Apache Commons IOのIOUtilsのようなユーティリティクラスを使うことができます。

ファイルを書き込む前に、ファイル全体を最初に読み取らないことが重要です。代わりに、それを小さな塊で行います。ここでストリームを使用し、バッファリングと小さなチャンクを除いてbyte []を処理するものは避けてください。

編集:あなたが一度にすべてのデータを記述する必要はありませんように ここではApache IOといくつかのコードが...

public static void main(String[] args) { 
    HttpExchange exchange = ...; 
    OutputStream responseBody = null; 

    try { 
     File file = new File("big-file.txt"); 
     long bytesToSkip = 4711; //detemine how many bytes to skip 

     exchange.sendResponseHeaders(200, file.length() - bytesToSkip); 
     responseBody = exchange.getResponseBody(); 
     skipAndCopy(file, responseBody, bytesToSkip);   
    } 
    catch (IOException e) { 
     // handle it 
    } 
    finally { 
     IOUtils.closeQuietly(responseBody); 
    } 
} 


private static void skipAndCopy(File src, @WillNotClose OutputStream dest, long bytesToSkip) throws IOException { 
    InputStream in = null; 

    try { 
     in = FileUtils.openInputStream(src); 

     IOUtils.skip(in, bytesToSkip); 
     IOUtils.copyLarge(in, dest); 
    } 
    finally { 
     IOUtils.closeQuietly(in); 
    } 
} 
+0

getBytes()に関する提案は例外を変更しません。私は毎回それを作成するのではなく、バイト[]への静的な参照を保持しようとしました。私はまだgetBytes()に関する提案は例外を変更しません。私は毎回それを作成するのではなく、バイト[]への静的な参照を保持しようとしました。私はまだ同じ例外を取得します。同じ例外を設定します。 – Sophie

+0

@Sophieこの男はそれをうまく説明しているだけで、実装の詳細を提供していません。Tarlogの解決策は間違っていました。彼は別の場所(静的フィールド)でファイル全体を読み上げていました。 – MarianP

+0

@Sophieデータ全体を保持するバイト[]を扱わないでください。アプリケーションのどの時点でもありません。代わりに、チャンク内のファイルを読み取り、レスポンスに「オンザフライ」で記述します。 'IOUtils'を見ることをお勧めします。 –

0

コードの問題myFile.getBytes()は、リクエストごとに新しい配列を作成します。

あなたは、単に文字列の代わりにバイト配列を保持することによってそれを改善することができます:ところで

 private static byte[] bytesToSend = "....................".getBytes(); //string about 8M 

    @Override 
    public void handle(HttpExchange exchange) throws IOException { 
      exchange.sendResponseHeaders(HTTP_OK, bytesToSend.length);          OutputStream responseBody = exchange.getResponseBody(); 
      responseBody.write(bytesToSend); 
      responseBody.close(); 
    } 

、このコードとあなたのコードを使用getBytes()の両方を。これは、デフォルトのプラットフォームエンコーディングを使用することを意味しますが、これは良い方法ではありません。 getBytes("UTF-8")

別の注記:実際のコードであると仮定してコードを修正しました。ロジックが複雑な場合(例:複数のファイルをダウンロードできるようにするには、ストリーミングを使用することをお勧めします。入力ファイルをチャンクで読み取り、チャンクをリクエストされたものに送信します。あまりにも多くのチャンクを記憶しないでください。

+0

getBytes()に関する提案は例外を変更しません。私は毎回それを作成するのではなく、バイト[]への静的な参照を保持しようとしました。そして、私はまだ同じ例外を取得します。 – Sophie

+0

私はあなたがこの問題を誤解していると思います。より多くの要求で空のメモリを失うことではなく、単一のファイルの問題が大きすぎます。 – MarianP

+0

@MarianP - しかし、それは大きなファイルを持つ5-7のクライアントで動作します。そのファイルが問題であるかどうかは分かりません。 – Sophie

4

利用の流れです。

getRequestBodyおよびgetResponseBodyを参照してください。ファイルをストリームとして開き、バイトを適切なストリームに書きたいと思うでしょう。

0

一度にバイトに文字列全体を変換しないでください:

Writer writer = new OutputStreamWriter(responseBody),someEncoding); 
try { 
    writer.write(myFile); 
} 
finally { 
    writer.close(); 
} 
5

あなたが一度にファイルのバイトの全てを取得した場合、それはメモリにそれらのすべてを読み、その後にそれらを記述することがありますファイルシステム。以下のような何かしてみてください:このような大量のデータを

FileReader reader = new FileReader(myFile); 
try{ 
    char buffer[] = new char[4096]; 
    int numberOfBytes=0; 
    while ((numberOfBytes=reader.read(buffer)) != -1){ 
     responseBody.write(buffer); 
    } 
}catch(Exception e){ 
    //TODO do something with the exception. 
}finally{ 
    reader.close(); 
} 
+0

getBytes()に関する提案は例外を変更しません。私は毎回それを作成するのではなく、バイト[]への静的な参照を保持しようとしました。そして、私はまだ同じ例外を取得します。 – Sophie

+0

バッファーに最適なサイズは何ですか?私はいつも "4096"も使用していますが、特に理由はありません。 – Michael

+3

多くのファイルシステムではデフォルトの割り当てサイズなので、ファイル操作には常に4096を使用します。 – ryanb

4

を、それがストリームデータに最適です。ストリーミングとは、一度にすべてを送信するのではなく、チャンクでデータを送信することを意味します。これは、すべてのデータをメモリに格納する必要がなく、メモリ内にすべてのデータを格納する必要がないため、メモリ効率が向上します。

また、ファイルデータを返すより一般的な方法は、Readerの代わりに通常のInputStreamを使用することです。

  • InputStream:データ
  • Readerのあらゆる種類の読み出しに使用:InputStreamを使用して

テキストデータを読み取るために使用を使用すると、文字エンコーディングを心配する必要がないことを意味します。バイナリファイルも送ることができるので、コードをより柔軟にすることができます。ここで

は完全なソリューションです:

OutputStream responseBody = null; 
try{ 
    File file = new File("bigggggg-text-file.txt"); 
    InputStream in = new FileInputStream(file); 
    exchange.sendResponseHeaders(HTTP_OK, file.length()); 
    responseBody = exchange.getResponseBody(); 
    int read; 
    byte buffer[] = new byte[4096]; 
    while ((read = in.read(buffer)) != -1){ 
    responseBody.write(buffer, 0, read); 
    } 
} catch (FileNotFoundException e){ 
    //uh-oh, the file doesn't exist 
} catch (IOException e){ 
    //uh-oh, there was a problem reading the file or sending the response 
} finally { 
    if (responseBody != null){ 
    responseBody.close(); 
    } 
} 
+0

+1私の意見では最高の答え。まさに私が意味するものだが、コードシュガーとよりよい英語で。 :) –

関連する問題