java.lang.OutOfMemoryError:bytea下载期间的Java堆空间

时间:2016-05-07 17:43:27

标签: java postgresql postgresql-9.3

我使用此代码从PostgreSQL下载bytea对象:

        Uri result = data.getData();
        String id = result.getLastPathSegment();
        cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?", new String[] { id }, null);
    int phoneIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DATA);

        if (cursor.moveToFirst()) {
                    PhoneNumber = cursor.getString(phoneIdx).toString();
                }

但是当我开始下载代码时,我得到了java.lang.OutOfMemoryError:此行上的Java堆空间:

public void initFileDBData() throws SQLException, IOException
{
    if (ds == null)
    {
        throw new SQLException("Can't get data source");
    }
    Connection conn = ds.getConnection();

    if (conn == null)
    {
        throw new SQLException("Can't get database connection");
    }

    PreparedStatement ps = null;

    try
    {
        conn.setAutoCommit(false);
        ps = conn.prepareStatement("SELECT * FROM PROCEDURE_FILES WHERE ID = ?");

        ps.setInt(1, id);
        ResultSet rs = ps.executeQuery();
        while (rs.next())
        {
            String file_name = rs.getString("FILE_NAME");
            InputStream binaryStreasm = rs.getBinaryStream("FILE");
            FacesContext fc = FacesContext.getCurrentInstance();
            ExternalContext ec = fc.getExternalContext();

            ec.responseReset();
            ec.setResponseContentLength(binaryStreasm.available());
            ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + file_name + "\"");

            byte[] buf;

                buf = new byte[binaryStreasm.available()];
                int offset = 0;
                int numRead = 0;
                while ((offset < buf.length) && ((numRead = binaryStreasm.read(buf, offset, buf.length - offset)) >= 0))
                {
                    offset += numRead;
                }

            HttpServletResponse response
                = (HttpServletResponse) FacesContext.getCurrentInstance()
                .getExternalContext().getResponse();

            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition", "attachment;filename=" + file_name);
            response.getOutputStream().write(buf);
            response.getOutputStream().flush();
            response.getOutputStream().close();
            FacesContext.getCurrentInstance().responseComplete();
        }

    }
    finally
    {
        if (ps != null)
        {
            ps.close();
        }
        conn.close();
    }
}

我可以以某种方式优化代码以消耗更少的内存吗?

更新的代码:

 buf = new byte[binaryStreasm.available()];

2 个答案:

答案 0 :(得分:1)

这可能是因为binaryStreasm.available()返回一个大值,以致它尝试创建一个无法容纳到内存中的字节数组,尝试设置小值5121024。 / p>

这里的另一个问题是你试图将bytea对象的整个内容加载到内存中,这不是正确的方法,特别是如果你必须处理这里的大二进制内容。您应该将内容写入response.getOutputStream()或首先将其写入临时文件,然后将文件内容写入response.getOutputStream()

答案 1 :(得分:1)

你依赖InputStream::available这是错误的。其documentation说:

  

返回估计可以从此输入流中读取(或跳过)的字节数,而不会被下一次调用此输入流的方法阻塞。下一次调用可能是同一个线程或另一个线程。单个读取或跳过这么多字节不会阻塞,但可以读取或跳过更少的字节。

     

请注意,虽然InputStream的某些实现将返回流中的总字节数,但许多实现不会。 使用此方法的返回值来分配用于保存此流中所有数据的缓冲区是绝对正确的。

(强调我的)

所以你面临两个问题:

  1. 如果您没有输出流的确切大小,请在Content-Length标题中传递什么号码?
  2. 如何将流从结果集传递到响应,而不会同时将所有数据保存在内存中。
  3. 我会通过稍微更改查询来解决第一个问题,以便它返回bytea字段的实际大小。

    ps = conn.prepareStatement("SELECT *,octet_length(FILE) as file_length FROM PROCEDURE_FILES WHERE ID = ?");
    

    PostgreSQL函数octet_length为您提供bytea列的长度(以字节为单位)。

    完成后,您可以使用

    ec.setResponseContentLength(rs.getInt("file_length"));
    

    现在,对于第二个问题,您应该避免将所有内容都读入大缓冲区。如果使用rs.getInt("file_length')中的数字来分配缓冲区,则会遇到相同的内存问题。你应该逐渐复制流。

    如果您有Apache Commons IO,则可以使用IOUtils.copy()将流从结果集复制到响应的输出流。在设置响应内容类型和长度之前,请避免获取二进制流,然后执行:

    IOUtils.copy( rs.getBinaryStream("FILE"), response.getOutputStream() );
    

    如果您不想使用Apache Commons IO,可以使用 small 缓冲区编写自己的循环。再次,首先设置响应内容类型和长度,然后执行类似

    的操作
    byte[] buffer = new byte[4096];
    
    try ( InputStream input = rs.getBinaryStream("FILE");
          OutputStream output = response.getOutputStream() ) {
    
        int numRead = 0;
    
        while ( ( numRead = input.read( buffer ) ) != -1 ) {
            output.write(buffer, 0, numRead );
        }
    
    }
    

    然后完成响应,关闭结果集,就完成了。我使用了try-with-resources语法,该语法在完成后自动关闭流。

    顺便说一句,没有理由使用while来读取单行,如果查询返回多行,则代码将无效。您可以使用简单的if (rs.next())并在else中抛出一些异常或向用户显示一些错误。