2010-06-09 3 views
2

아파치 커먼즈 파일 업로드를 사용하여 다중 파일 업로드를 수행하기 위해 서블릿을 사용하고 있습니다. 내 코드의 일부가 아래에 게시됩니다. 내 문제는 한 번에 여러 파일을 업로드하면 앱 서버의 메모리 사용량이 급격히 증가한다는 것입니다. 이것은 파일 업로드가 끝날 때까지만 가능하지만 앱 서버가 메모리에 매달려서 OS로 되돌려 보내지 않는 것처럼 보이는 것이 좋습니다. 나는 이것을 생산에 넣었을 때 서버에서 메모리 부족 예외가 발생하게 될 것이라고 걱정합니다. 왜 이런 일이 일어나고 있는지에 대한 아이디어가 있습니까? 나는 서버가 세션을 시작했을 것으로 생각하고 그것이 만료 된 후에 메모리를 되돌려 줄 것이다. 그러나 나는 100 % 긍정적이지 않다.서블릿 파일 업로드 메모리 사용량

if (ServletFileUpload.isMultipartContent(request)) { 
     ServletFileUpload upload = new ServletFileUpload(); 
     FileItemIterator iter = upload.getItemIterator(request); 
     while (iter.hasNext()) { 
      FileItemStream license = iter.next(); 
      if (license.getFieldName().equals("upload_button") || license.getName().equals("")) { 
       continue; 
      } 
      // DataInputStream stream = new DataInputStream(license.openStream()); 
      InputStream stream = license.openStream(); 
      List<Integer> byteArray = new ArrayList<Integer>(); 
      int tempByte; 
      do { 
       tempByte = stream.read(); 
       byteArray.add(tempByte); 
      } while (tempByte != -1); 
      stream.close(); 
      byteArray.remove(byteArray.size() - 1); 
      byte[] bytes = new byte[byteArray.size()]; 
      int i = 0; 
      for (Integer tByte : byteArray) { 
       bytes[i++] = tByte.byteValue(); 
      } 
     } 
    } 

미리 감사드립니다.

+0

@skaffman과 @ Bozho의 제안을 반영하도록 코드를 업데이트했습니다. 이제는 사용자 정의 매개 변수를 전달하는 DiskFileItemFactory를 사용하여 ServletFileUpload 객체를 만듭니다. 나는 또한 올바른 방법으로 InputStream을 처리하고 finally 블록에서이를 닫는다. 그러나 여전히 문제가 해결되지 않은 것 같습니다. 여기 내가 생각할 수있는 일이 있습니다. 더비 데이터베이스를 사용하고 있는데이 서버는 서버와 동일한 스레드에서 열리는 것으로 보입니다. 나는 또한 물체를 얼룩으로 저장하고있다. 데이터베이스 자체가 메모리로 읽혀지고 보유되고 있습니까 ?? 지금까지 모든 위대한 의견에 감사드립니다! Scott – Scott

+0

솔루션을 찾을 수 있었습니까? 나는 똑같은 문제를 겪고있다! –

+0

한 가지 질문으로 유명한 SO-ers를 보지 못했습니다. – gkiko

답변

0

(적어도 자바 7까지) 자바 스트림을 처리하는 올바른 방법은 다음과 같습니다

InputStream is; 
try { 
    is = ... 
} catch (IOEXception ex) { 
    // report exception - print, or throw a wrapper 
} finally { 
    try { 
     is.close(); 
    } catch (IOException ex) {} 
} 

당신 가까이하지 않으면

(아마도뿐만 아니라 두 번째 캐치에 예외를 로깅) 귀하의 스트림, 메모리는 garbage collector에 의해 해방되지 않습니다.

2

ServletFileUpload을 구성 할 때 기본값을 사용하는 대신 자신이 구성한 개체 (특히 DiskFileItemFactory)를 전달해야합니다. 기본값은 사용자의 요구 사항, 특히 대량 생산 환경에서는 적합하지 않을 수 있습니다. 여기

ArrayList<Integer> byteArray = new ArrayList<Integer>(); 
int tempByte; 
do { 
tempByte = stream.read(); 
byteArray.add(tempByte); 

당신은 정수 배열에서 바로 메모리에 모든 바이트를 작성

+0

그는 이미 ""스트리밍 모드 "(http://commons.apache.org/fileupload/streaming.html)를 사용하고 있습니다. 그러나 그는 이미 너무 많은 메모리를 할당함으로써 이익을 얻지 못합니다. – BalusC

1

! 모든 정수는 4 바이트의 메모리를 소비하지만 모든 읽기 바이트에 대해 1 바이트 만 필요합니다. 효과적으로 ArrayList<Byte> 또는 더 나은 byte[]을 사용해야합니다. 왜냐하면 모든 byte은 메모리가 1 바이트에 불과하기 때문입니다.하지만 saldo 당 하나의 파일은 여전히 ​​큰 파일만큼 많은 메모리를 할당합니다. 큰 파일이 그대로

그리고 여기

byte[] bytes = new byte[byteArray.size()]; 

당신은 이후 많은 메모리를 할당하고 있습니다. Saldo 당 당신은 ArrayList<Integer>byte[] 두 가지를 가지고 있는데, 큰 파일만큼 5 배 많은 메모리를 할당합니다.

낭비입니다.

OutputStream바로에 작성해야합니다. FileOutputStream.

InputStream input = null; 
OutputStream output = null; 
try { 
    input = license.openStream(); 
    output = new FileOutputStream("/file.ext"); 
    byte[] buffer = new byte[1024]; 
    for (int length; (length = input.read(buffer)) > 0;) { 
     output.write(buffer, 0, length); 
    } 
} finally { 
    if (output != null) try { output.close(); } catch (IOException logOrIgnore) {} 
    if (input != null) try { input.close(); } catch (IOException logOrIgnore) {} 
} 

바이트의 전체 파일 길이 (또는 정수를 사용하는 경우 4 배) 대신 버퍼의 메모리가 1KB 만 효율적입니다.

이 실제로 인 경우 byte[]에 넣으려면 ArrayList<Integer> 단계 건너 뛰기 만하면됩니다. 그것은 말도 안돼. ByteArrayOutputStreamOutputStream으로 사용하십시오.큰 파일이 그대로

InputStream input = null; 
ByteArrayOutputStream output = null; 
try { 
    input = license.openStream(); 
    output = new ByteArrayOutputStream(); 
    byte[] buffer = new byte[1024]; 
    for (int length; (length = input.read(buffer)) > 0;) { 
     output.write(buffer, 0, length); 
    } 
} finally { 
    if (output != null) try { output.close(); } catch (IOException logOrIgnore) {} 
    if (input != null) try { input.close(); } catch (IOException logOrIgnore) {} 
} 

byte[] bytes = output.toByteArray(); 

그러나 이것은 아직도 당신이 처음에 byte[] 이후 ArrayList<Integer>로했고, 그것을 더 이상 지금 만 파일 크기가 아닌 5 번입니다, 많은 메모리를 요한다.


업데이트 : 데이터베이스에서이 저장하고 싶은 의견에 따라. 전체 파일을 Java 메모리에 저장하지 않고도이 작업을 수행 할 수 있습니다. 단지 PreparedStatement#setBinaryStream()을 사용하여 얻은 InputStream을 바로으로 작성하십시오.

final String SQL = "INSERT INTO file (filename, contentType, content) VALUES (?, ?, ?)"; 
String filename = FilenameUtils.getName(license.getName()); 
InputStream input = license.openStream(); 

Connection connection = null; 
PreparedStatement statement = null; 
try { 
    connection = database.getConnection(); 
    statement = connection.prepareStatement(SQL); 
    statement.setString(1, filename); 
    statement.setString(2, getServletContext().getMimeType(filename)); 
    statement.setBinaryStream(3, input); 
    statement.executeUpdate(); 
} catch (SQLException e) { 
    throw new ServletException("Saving file in DB failed", e); 
} finally { 
    if (statement != null) try { statement.close(); } catch (SQLException logOrIgnore) {} 
    if (connection != null) try { connection .close(); } catch (SQLException logOrIgnore) {} 
} 
+0

이 예를 들어 주셔서 감사합니다! 정수 배열에 모든 것을 넣는 이유는 read() 메서드가 int를 반환하기 때문입니다. JavaDoc을 보면 0에서 255 사이의 int를 반환하는 것처럼 보이므로 바이트로 안전하게 캐스팅 할 수 있다고 가정합니다. 당신이 마지막으로 본보기라고 생각하는 것은 필자가 필요로하는 것입니다. 왜냐하면 파일 전체를 블로그에 데이터베이스에 저장해야하기 때문입니다. 감사합니다! – Scott

+0

그 경우,'PreparedStatement # setBinaryStream()'을 사용하는 것이 가장 메모리 효율적입니다. – BalusC

+0

이것은 영속성을 위해 Hibernate를 사용하고 있지만 의미가 있습니다. 메모리에 전부 보관하지 않고 최대 절전 모드로 저장할 수 있습니까? – Scott