0

我有 java 6 嵌入式 HttpServer。它有一个句柄,允许客户下载一个大文本文件。问题是当服务器有超过 10 个并发客户端时,我会出现内存不足异常。我很确定问题出在 Http Server 周围。

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

这是低效的,您需要堆空间来存储整个文件数据。当您第一次完整地读取文件然后完整地写入文件时,您会浪费大量的堆空间。

而是以特定大小的块从文件直接读取/写入文件数据到响应。您可以自己编写代码,也可以只使用IOUtilsApache 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

使用流,这样您就不必一次写入所有数据。

请参阅getRequestBodygetResponseBody。您需要将文件作为流打开并将字节写入适当的流。

于 2011-10-26T14:30:49.317 回答
4

对于这样的大量数据,最好对数据进行流式传输。流式传输意味着您以块的形式发送数据,而不是一次发送所有数据。这更节省内存,因为您不必将所有数据存储在内存中,只需存储其中的一部分即可。

此外,返回文件数据的更通用方法是使用常规InputStream而不是Reader.

  • InputStream: 用于读取任何类型的数据
  • Reader: 用于读取文本数据

使用 anInputStream意味着您不必担心字符编码。它还使您的代码更加灵活,因为它也允许您发送二进制文件。

这是一个完整的解决方案:

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

您的代码中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 回答
0

不要一次将整个字符串转换为字节:

Writer writer = new OutputStreamWriter(responseBody),someEncoding);
try {
  writer.write(myFile);
}
finally {
  writer.close();
}
于 2011-10-26T14:30:54.040 回答