2010-06-09 68 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