2010-06-09 52 views
2

我正在使用servlet來執行多文件上載(使用apache commons fileupload)。我的代碼的一部分張貼在下面。我的問題是,如果我一次上傳多個文件,應用程序服務器的內存消耗會大幅跳躍。如果直到文件上傳完成,這可能是確定的,但應用程序服務器似乎掛在內存上,並且不會將其返回到操作系統。我很擔心,當我把它投入生產時,我最終會在服務器上發生內存不足異常。任何想法爲什麼發生這種情況?我想服務器可能已經啓動了一個會話,並會在內存過期後返回,但我不是100%肯定的。Servlet文件上傳內存消耗

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塊中關閉它。但是,這似乎還沒有解決問題。這是我認爲可能發生的事情。我正在使用一個derby數據庫,它似乎是在與服務器相同的線程中打開的。我也將對象存儲爲Blob。數據庫本身是否被讀入內存並被保存? 感謝所有迄今爲止的出色反饋! Scott – Scott 2010-06-09 22:42:33

+0

您是否設法找到解決方案?我有同樣的問題! – 2012-06-06 00:02:06

+0

我在一個問題中沒有見過這麼多着名的SO-ers :) – gkiko 2015-03-11 13:04:53

回答

0

來處理Java的流(至少在Java 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時,您應該傳遞一個您自己配置的FileItemFactory對象(具體而言,是DiskFileItemFactory),而不是依賴於默認值。默認值可能不適合您的要求,尤其是在大批量生產環境中。

+0

他已經在使用[「流模式」](http://commons.apache.org/fileupload/streaming.html)。但是,他自己已經分配了太多的內存,並沒有從中受益。 – BalusC 2010-06-10 21:25:40

1

這裏

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

你的每一個字節寫直入內存整型數組!每個整數佔用4個字節的內存,而每個讀取字節只需要一個字節。實際上,您應該使用ArrayList<Byte>或更好的byte[],因爲每個byte只需要一個字節的內存,但是每個saldo仍會分配儘可能多的內存。

這裏

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

你以後分配儘可能多的內存一樣大的文件。根據文件大小的不同,你可以使用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>步驟。這個不成立。使用ByteArrayOutputStream作爲OutputStream

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

然而,這仍然花費盡可能多的內存一樣大的文件,這只是現在的文件大小不是5次了,因爲你最初與ArrayList<Integer>做和byte[]之後。


更新:您願意爲每次您的評論作爲其存儲在數據庫中。您也可以在不將整個文件存儲在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之間的整數,所以我想我可以安全地轉換成一個字節。我認爲你是最後一個例子,因爲我需要將整個文件作爲博客存儲在數據庫中。 謝謝! – Scott 2010-06-11 15:17:05

+0

在這種情況下,使用'PreparedStatement#setBinaryStream()'是最有效的內存。 – BalusC 2010-06-11 15:24:18

+0

這是有道理的,但我使用Hibernate進行持久化。有沒有辦法通過休眠來存儲它,而不是全部存儲在內存中? – Scott 2010-06-15 16:57:06