0

私はJava 6組み込みHttpServerを持っています。クライアントが大きなテキスト ファイルをダウンロードできるようにするハンドルがあります。問題は、サーバーに 10 を超える同時クライアントがある場合、メモリ不足の例外が発生することです。問題は Http サーバーにあると確信しています。

   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();
         } 
   }

今私が得る例外は次のとおりです:

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() に関する提案は、例外を変更しません。毎回作成するのではなく、byte[] への静的参照を保持しようとしました。そして、私はまだ同じ例外を受け取ります。

4

6 に答える 6

7

大きなファイルにはそうしないでください:

byte[] bytesToSend = myFile.getBytes();

これは非効率的であり、ファイル データ全体を格納するためのヒープ スペースが必要です。最初にファイルを完全に読み取り、その後完全に書き込むと、多くのヒープスペースが無駄になります。

代わりに、特定のサイズのチャンクでファイル データをファイルから直接応答に読み書きします。自分でコードを書くこともIOUtils、Apache Commons IO のようなユーティリティ クラスを使用することもできます。

ファイルを書き込む前に、最初にファイル全体を読み取らないことが重要です。代わりに、より小さなチャンクで実行してください。ここではストリームを使用し、バッファリングと小さなチャンクを除いて 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);
    }
}
于 2011-10-26T14:30:23.707 に答える
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();
}
于 2011-10-26T14:32:52.450 に答える
4

一度にすべてのデータを書き込む必要がないように、ストリームを使用します。

getRequestBodyおよびgetResponseBodyを参照してください。ファイルをストリームとして開き、バイトを適切なストリームに書き込みます。

于 2011-10-26T14:30:49.317 に答える
4

このような大量のデータでは、データをストリーミングするのが最善です。ストリーミングとは、データを一度に送信するのではなく、チャンクで送信することを意味します。これは、すべてのデータをメモリに保存する必要がなく、その一部だけを保存する必要があるため、メモリ効率が高くなります。

また、ファイル データを返すより一般的な方法はInputStreamReader.

  • 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();
  }
}
于 2011-10-26T15:12:11.817 に答える
0

String 全体を一度にバイトに変換しないでください。

Writer writer = new OutputStreamWriter(responseBody),someEncoding);
try {
  writer.write(myFile);
}
finally {
  writer.close();
}
于 2011-10-26T14:30:54.040 に答える
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")

別の注意:実際のコードであると仮定して、コードを修正しました。複数のファイルのダウンロードを許可するなど、ロジックがより複雑な場合は、ストリーミングを使用することをお勧めします。入力ファイルをチャンクごとに読み取り、チャンクを要求された場所に送信します。メモリ内にあまり多くのチャンクを保持しないでください。

于 2011-10-26T14:29:42.197 に答える