2011-10-26 2 views
0

자바 6 임베디드 HttpServer가 있습니다. 그것은 클라이언트가 큰 텍스트 파일을 다운로드 할 수있는 핸들이 있습니다. 문제는 서버가 10 개 이상의 동시 클라이언트를 가지고있을 때 메모리 예외가 발생한다는 것입니다. 나는 그 문제가 Http Server 주위에 있는지 잘 알고있다. java.lang.OutOfMemoryError on HttpServer on 대용량 데이터 다운로드

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

지금 내가 얻을 예외은 : getBytes() 예외를 변경하지 않습니다에 대한

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: 

제안. 나는 매번 그것을 생성하는 대신에 바이트 []에 대한 정적 참조를 유지하려고 시도했다. 그리고 나는 여전히 같은 예외를 얻는다.

+0

하십시오이 한 번에이 문제가 될 수처럼 코드 주위에 약간의 "코드 따옴표".. – kgautron

+0

@Sophie이 소리; Java 문제 및 스레드 처리 문제가 있습니다. HTTP 서버에 얼마나 많은 연결을 허용합니까? 어떤 HTTP 서버를 사용하고 있습니까? – bakoyaro

+0

@Sophie 또한 소스를 컴파일 할 때 debug = true로 설정하면 스택 추적에서 줄 번호를 가져와야합니다. – bakoyaro

답변

7

큰 파일을 위해 그렇게하지 마십시오 : 이것은 비효율적이며 전체 파일의 데이터를 저장하는 힙 공간이 필요

byte[] bytesToSend = myFile.getBytes(); 

. 처음에는 파일을 완전히 읽었을 때 많은 양의 힙 공간을 낭비하고 이후에는 완벽하게 씁니다.

대신 특정 크기의 청크 파일 데이터를 파일에서 응답으로 직접 읽고 씁니다. 직접 코드를 작성하거나 Apache Commons IO에서 IOUtils과 같은 유틸리티 클래스를 사용할 수 있습니다.

쓰기 전에 전체 파일을 먼저 읽지 않는 것이 중요합니다. 대신 작은 덩어리로하십시오. 여기서 스트림을 사용하고 버퍼링과 작은 덩어리를 제외하고는 byte []를 다루는 것을 피하십시오.

편집 : 한 번에 모든 데이터를 기록 할 필요가 없도록 여기

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

getBytes()에 관한 제안은 예외를 변경하지 않습니다. 나는 매번 그것을 생성하는 대신에 바이트 []에 대한 정적 참조를 유지하려고 시도했다. 그리고 여전히 getBytes()에 관한 제안은 예외를 변경하지 않습니다. 나는 매번 그것을 생성하는 대신에 바이트 []에 대한 정적 참조를 유지하려고 시도했다. 그리고 나는 여전히 같은 예외를 둡니다. 동일한 예외를 두십시오. – Sophie

+0

@Sophie이 사람은 잘 설명하고 있지만 구현 세부 정보는 제공하지 않습니다.Tarlog의 해결책은 틀렸고, 다른 장소 (정적 필드)에서 전체 파일을 읽었습니다. – MarianP

+0

@Sophie 전체 데이터를 보유하는 byte []를 처리하지 마십시오. 귀하의 신청서의 어느 시점에서든. 대신 파일을 청크로 읽고 "on-the-fly"로 응답에 기록하십시오. 'IOUtils'를 보길 권합니다. –

0

myFile.getBytes()은 각 요청에 대해 새 배열을 만드는 코드의 문제입니다.

당신은 단순히 문자열 대신 바이트 배열을 유지하여 향상시킬 수 Btw는

 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")

다른 메모 : 코드가 실제 코드라고 가정하고 수정했습니다. 논리가 더 복잡한 경우 (예 : 여러 개의 파일을 다운로드 할 수있게하려면 스트리밍을 사용하는 것이 더 좋습니다. 입력 파일을 청크별로 읽고 청크를 요청 된 사람에게 보냅니다. 너무 많은 덩어리를 기억하지 마십시오.

+0

getBytes()에 대한 제안은 예외를 변경하지 않습니다. 나는 매번 그것을 생성하는 대신에 바이트 []에 대한 정적 참조를 유지하려고 시도했다. 그리고 나는 여전히 같은 예외를 얻는다. – Sophie

+0

나는 당신이 그 문제를 잘못 이해했다고 생각합니다. 더 많은 요청으로 빈 메모리를 잃어 버리는 것이 아니라, 너무 큰 단일 파일의 문제입니다. – MarianP

+0

@MarianP -하지만 파일 크기가 큰 5-7 명의 고객에게 적합합니다. 파일이 문제가되는지 확실하지 않습니다. – Sophie

4

를 사용하여 스트림 ... 아파치 IO 일부 코드입니다.

getRequestBodygetResponseBody을 참조하십시오. 파일을 스트림으로 열고 해당 스트림에 바이트를 쓰려고 할 것입니다.

0

한 번에 바이트로 전체 문자열을 변환하지 마십시오

Writer writer = new OutputStreamWriter(responseBody),someEncoding); 
try { 
    writer.write(myFile); 
} 
finally { 
    writer.close(); 
} 
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(); 
} 
+0

getBytes()에 관한 제안은 예외를 변경하지 않습니다. 나는 매번 그것을 생성하는 대신에 바이트 []에 대한 정적 참조를 유지하려고 시도했다. 그리고 나는 여전히 같은 예외를 얻는다. – Sophie

+0

버퍼에 가장 적합한 크기를 아십니까? 나는 항상 "4096"도 사용하지만 특별한 이유는 없습니다. – Michael

+3

많은 파일 시스템의 기본 할당 크기이기 때문에 항상 파일 조작에 4096을 사용합니다. – ryanb

4

를, 그것은 스트림 데이터에 가장 좋습니다. 스트리밍이란 데이터를 한꺼번에 보내는 대신 청크로 보내는 것을 의미합니다. 이것은 모든 데이터를 메모리에 저장할 필요가 없으므로 메모리 효율이 높습니다.

또한 파일 데이터를 반환하는 일반적인 방법은 대신 일반 InputStream을 사용하는 것입니다.

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

+1 내 의견에 고맙습니다. 정확하게 내가 의미하는 바를 약간의 코드 - 설탕과 더 나은 영어로. :) –