我有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 []的静态引用,而不是每次都创建它。我仍然得到同样的例外。
答案 0 :(得分: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);
}
}
答案 1 :(得分: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();
}
答案 2 :(得分:4)
使用流,这样您就不必一次写入所有数据。
请参阅getRequestBody和getResponseBody。您需要将文件作为流打开,并将字节写入适当的流。
答案 3 :(得分:4)
对于这样的大量数据,最好流式传输数据。流式传输意味着您可以以块的形式发送数据,而不是一次性发送数据。这样可以提高内存效率,因为您不必将所有数据存储在内存中,只需将其中的一部分存储起来。
此外,返回文件数据的更通用方法是使用常规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();
}
}
答案 4 :(得分: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")
另一个注意事项:我更正了你的代码,假设它是一个真正的代码。如果您的逻辑更复杂,例如您允许下载多个文件,最好使用流式传输:按块读取输入文件并将块发送到请求的块。不要在记忆中留下太多的块。
答案 5 :(得分:0)
不要一次将整个String转换为字节:
Writer writer = new OutputStreamWriter(responseBody),someEncoding);
try {
writer.write(myFile);
}
finally {
writer.close();
}