我正在使用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();
}
}
}
提前致谢!!
答案 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将不会释放内存。