Servlet文件上传内存消耗

时间:2010-06-09 21:25:15

标签: java servlets file-upload

我正在使用servlet来执行多文件上载(使用apache commons fileupload)。我的部分代码发布在下面。我的问题是,如果我一次上传几个文件,应用服务器的内存消耗会大幅上升。如果仅在文件上传完成之前,这可能是正常的,但应用服务器似乎挂在内存上并且永远不会将其返回到操作系统。我担心当我把它投入生产时,我最终会在服务器上出现内存不足的异常。有关为什么会发生这种情况的任何想法?我认为服务器可能已启动会话并在内存过期后将内存恢复,但我不是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();
            }
        }
    }

提前致谢!!

3 个答案:

答案 0 :(得分:2)

构建ServletFileUpload时,您应该传入自己配置的FileItemFactory对象(特别是DiskFileItemFactory),而不是依赖于默认值。默认值可能不适合您的要求,尤其是在大批量生产环境中。

答案 1 :(得分:2)

下面

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

你之后分配的内存与文件大一样多。每个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) {}
}

这对于缓冲区而言实际上只有1KB的内存而不是整个文件的字节长度(或者使用整数时的4倍)。

或者如果确实希望将其放在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) {}
}

答案 2 :(得分: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将不会释放内存。