Question

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.

Was it helpful?

Solution

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

OTHER TIPS

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

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.

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

Do not convert the whole String into bytes at once:

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

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top