40

我正在使用 IBM Websphere Application Server v6 和 Java 1.4,并尝试将大型 CSV 文件写入 CSV 文件以ServletOutputStream供用户下载。目前文件大小在 50-750MB 之间。

较小的文件不会造成太大的问题,但是对于较大的文件,它似乎被写入堆中,然后导致 OutOfMemory 错误并关闭整个服务器。

这些文件只能通过 HTTPS 提供给经过身份验证的用户,这就是为什么我通过 Servlet 为它们提供服务,而不是仅仅将它们粘贴在 Apache 中。

我正在使用的代码是(在此周围删除了一些绒毛):

    resp.setHeader("Content-length", "" + fileLength);
    resp.setContentType("application/vnd.ms-excel");
    resp.setHeader("Content-Disposition","attachment; filename=\"export.csv\"");

    FileInputStream inputStream = null;

    try
    {
        inputStream = new FileInputStream(path);
        byte[] buffer = new byte[1024];
        int bytesRead = 0;

        do
        {
            bytesRead = inputStream.read(buffer, offset, buffer.length);
            resp.getOutputStream().write(buffer, 0, bytesRead);
        }
        while (bytesRead == buffer.length);

        resp.getOutputStream().flush();
    }
    finally
    {
        if(inputStream != null)
            inputStream.close();
    }

FileInputStream似乎不会导致问题,就像我写入另一个文件或只是完全删除写入内存使用似乎不是问题一样。

我在想的是,resp.getOutputStream().write它被存储在内存中,直到数据可以发送到客户端。所以整个文件可能会被读取并存储在resp.getOutputStream()导致我的内存问题和崩溃中!

我尝试过缓冲这些流,也尝试过使用 Channels from java.nio,但似乎都没有对我的内存问题产生任何影响。我还刷新了OutputStream循环的每次迭代和循环之后,这没有帮助。

4

10 回答 10

46

默认情况下,一般的 servletcontainer 本身每 ~2KB 刷新一次流。当从同一个源顺序流式传输数据时,您flush()真的不需要显式调用at 。例如,在 Tomcat(和 Websphere!)中,这可配置为HTTP 连接器的属性。OutputStreamHttpServletResponsebufferSize

如果事先不知道内容长度(根据Servlet API 规范!)并且客户端支持 HTTP 1.1,那么一般的 servletcontainer 也只是以块的形式流式传输数据。

问题症状至少表明 servletcontainer 在刷新之前正在缓冲内存中的整个流。这可能意味着内容长度标头未设置和/或 servletcontainer 不支持分块编码和/或客户端不支持分块编码(即它使用 HTTP 1.0)。

要修复一个或另一个,只需预先设置内容长度:

response.setContentLengthLong(new File(path).length());

或者当您还没有使用 Servlet 3.1 时:

response.setHeader("Content-Length", String.valueOf(new File(path).length()));
于 2010-06-16T12:09:02.957 回答
1

flush输出流有效。

真的,我想评论一下,您应该使用三参数形式的写入,因为缓冲区不一定是完全读取的(特别是在文件末尾(!))。除非您希望您的服务器意外死亡,否则尝试/最终将是有序的。

于 2009-03-26T11:44:53.837 回答
1

我使用了一个包装输出流的类,以使其在其他上下文中可重用。它在更快地将数据传输到浏览器方面对我来说效果很好,但我还没有研究内存影响。(请原谅我过时的 m_ 变量命名)

import java.io.IOException;
import java.io.OutputStream;

public class AutoFlushOutputStream extends OutputStream {

    protected long m_count = 0;
    protected long m_limit = 4096; 
    protected OutputStream m_out;

    public AutoFlushOutputStream(OutputStream out) {
        m_out = out;
    }

    public AutoFlushOutputStream(OutputStream out, long limit) {
        m_out = out;
        m_limit = limit;
    }

    public void write(int b) throws IOException {

        if (m_out != null) {
            m_out.write(b);
            m_count++;
            if (m_limit > 0 && m_count >= m_limit) {
                m_out.flush();
                m_count = 0;
            }
        }
    }
}
于 2009-03-26T15:32:02.827 回答
1

我也不确定flush()onServletOutputStream在这种情况下是否有效,但ServletResponse.flushBuffer()应该将响应发送给客户端(至少按照 2.3 servlet 规范)。

ServletResponse.setBufferSize()听起来也很有希望。

于 2009-03-30T18:42:34.957 回答
1

所以,按照你的场景,你不应该在那个while循环中(每次迭代)而不是在它之外刷新(ing)吗?我会尝试这样做,不过缓冲区要大一点。

于 2010-06-16T10:20:41.713 回答
1
  1. 如果 close() 运算符中的字段不为空, Kevin 的班级应该关闭该m_out字段,我们不想泄漏东西,是吗?

  2. 除了ServletOutputStream.flush()操作员之外,该HttpServletResponse.flushBuffer()操作还可以刷新缓冲区。但是,关于这些操作是否有任何影响,或者 http 内容长度支持是否受到干扰,这似乎是一个特定于实现的细节。请记住,指定内容长度是 HTTP 1.0 上的一个选项,因此如果您刷新内容,则内容应该只是流出。但我没有看到

于 2010-06-21T16:17:06.150 回答
1

while 条件不起作用,您需要在使用前检查 -1。并且请为输出流使用一个临时变量,它更易于阅读并且可以安全地重复调用 getOutputStream()。

OutputStream outStream = resp.getOutputStream();
while(true) {
    int bytesRead = inputStream.read(buffer);
    if (bytesRead < 0)
      break;
    outStream.write(buffer, 0, bytesRead);
}
inputStream.close();
out.close();
于 2011-06-24T10:17:38.317 回答
0

与您的内存问题无关,while循环应该是:

while(bytesRead > 0);
于 2009-03-26T14:08:51.307 回答
0

你的代码有一个无限循环。

do
{
    bytesRead = inputStream.read(buffer, offset, buffer.length);
    resp.getOutputStream().write(buffer, 0, bytesRead);
}
while (bytesRead == buffer.length);

offset在整个循环中具有相同的值,因此如果最初offset = 0,它将在每次迭代中保持不变,这将导致无限循环并导致 OOM 错误。

于 2014-04-25T07:20:38.043 回答
-1

默认情况下,IBM websphere 应用程序服务器对 servlet 使用异步数据传输。这意味着它缓冲响应。如果您遇到大数据和 OutOfMemory 异常的问题,请尝试更改 WAS 上的设置以使用同步模式。

将 WebSphere Application Server WebContainer 设置为同步方式

您还必须注意加载块并刷新它们。从大文件加载的示例。

ServletOutputStream os = response.getOutputStream();
FileInputStream fis = new FileInputStream(file);
            try {
                int buffSize = 1024;
                byte[] buffer = new byte[buffSize];
                int len;
                while ((len = fis.read(buffer)) != -1) {
                    os.write(buffer, 0, len);
                    os.flush();
                    response.flushBuffer();
                }
            } finally {
                os.close();
            }
于 2015-07-07T11:20:10.533 回答