下载大数据时 HttpServer 上的 java.lang.OutOfMemoryError

发布于 2024-12-11 18:21:54 字数 2041 浏览 0 评论 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[] 的静态引用,而不是每次都创建它。我仍然遇到同样的异常。

I have java 6 embedded HttpServer. It has a handle which allows clients to download a big text file. The problem is that whenthe server has more then 10 simultaneous clients, i get out of memory exception. I'm prety sure that the problem is around the 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();
         } 
   }

Now the exception i get is:

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: 

The suggestion regarding the getBytes() doesn't change the exception. i have tried to hold a static reference to byte[] instead of creating it each time. And I still get the same exception.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

女皇必胜 2024-12-18 18:21:54

不要对大文件这样做:

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

Do not do that for large files:

byte[] bytesToSend = myFile.getBytes();

This is inefficient and you need heap space for storing the whole file data. You're wasting lots of heap space when you first read the file completly and afterwards write it completly.

Instead read/write the file data in chunks of specific size from file directly to the response. You can write code on your own or just use a utility class like IOUtils from Apache Commons IO.

It is important to not read the whole file first before you write it. Instead do it in smaller chunks. Use streams here and avoid anything that deals with byte[] except for buffering and the small chunks.

Edit:
Here's some code with 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);
    }
}
另类 2024-12-18 18:21:54

如果您一次检索文件的所有字节,它必须将所有字节读入内存,然后将它们写入文件系统。尝试类似的东西:

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

If you retrieve all of the bytes for the file at once, it has to read all of them into memory and then write them to the filesystem. try something like:

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();
}
肥爪爪 2024-12-18 18:21:54

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

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

Use streams so that you don't have to write all the data at once.

See getRequestBody and getResponseBody. You'll want to open your file as a stream and write the bytes to the appropriate stream.

兮子 2024-12-18 18:21:54

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

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

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

With large amounts of data like this, it's best to stream the data. Streaming means that you send the data in chunks instead of sending it all at once. This is more memory-efficient because you don't have to store all the data in memory, just pieces of it.

Also, a more generic way of returning the file data is to use a regular InputStream instead of a Reader.

  • InputStream: used for reading any kind of data
  • Reader: used for reading text data

Using an InputStream means you don't have to worry about character encodings. It also makes your code more flexible because it allows you to send binary files too.

Here is a complete solution:

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();
  }
}
零度℉ 2024-12-18 18:21:54

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

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

Do not convert the whole String into bytes at once:

Writer writer = new OutputStreamWriter(responseBody),someEncoding);
try {
  writer.write(myFile);
}
finally {
  writer.close();
}
揽月 2024-12-18 18:21:54

代码中的问题是 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")

另请注意:我更正了您的代码,假设它是真实的代码。如果您的逻辑更复杂,例如您允许下载多个文件,最好使用流式传输:按块读取输入文件并将块发送到请求的位置。不要在内存中保留太多块。

The problem in your code that myFile.getBytes() creates a new array for each request.

You can simply improve it by holding the byte array instead of String:

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

Btw, both this code and your code use getBytes(). This means that it will use the default platform encoding, which is not a good practice. It's better to call it with an explicit encoding, like getBytes("UTF-8")

Another note: I corrected your code assuming it's a real code. In case your logic is more complex, e.g. you allow downloading multiple files, it's better to use streaming: read the input file by chunks and send the chunks to the requested. Don't keep too many chunks in memory.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文