代码之家  ›  专栏  ›  技术社区  ›  Amit

如何克服大文件写入过程中的OutOfMemoryError

  •  4
  • Amit  · 技术社区  · 15 年前

    我面临的问题是,由于某些表的文件很大(约30GB),所以即使使用20GB的Java堆,我几乎每次都会耗尽内存。在创建文件的过程中,当文件大小超过堆大小时,即使使用最激进的GC策略之一,该过程似乎也会挂断。例如,如果文件大小为>20GB,堆大小为20GB,则一旦堆利用率达到最大堆大小,其每分钟写入2MB左右的速度就会减慢,以这种速度,将需要数月才能获得完全提取。

    我正在寻找克服这个问题的方法。任何帮助都将不胜感激。

    Java-JDK1.6.0\u 14

    系统配置-运行在4 X Intel Xeon E7450(6核)上的RH Enterprise Linux(2.6.18)@2.39GH

    内存-32GB

    数据库Oracle 11g

    文件连接部分代码如下:

    private void runQuery(Connection conn, String query, String filePath,
            String fileName) throws SQLException, Exception {
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try {
            stmt = conn.prepareStatement(query,
                    ResultSet.TYPE_SCROLL_INSENSITIVE,
                    ResultSet.CONCUR_READ_ONLY);
            stmt.setFetchSize(maxRecBeforWrite);
            rs = stmt.executeQuery();
            // Write query result to file
            writeDataToFile(rs, filePath + "/" + fileName, getRecordCount(
                    query, conn));
        } catch (SQLException sqle) {
            sqle.printStackTrace();
        } finally {
            try {
                rs.close();
                stmt.close();
            } catch (SQLException ex) {
                throw ex;
            }
        }
    }
    
    private void writeDataToFile(ResultSet rs, String tempFile, String cnt)
            throws SQLException, Exception {
        FileOutputStream fileOut = null;
        int maxLength = 0;
        try {
            fileOut = new FileOutputStream(tempFile, true);
            FileChannel fcOut = fileOut.getChannel();
    
            List<TableMetaData> metaList = getMetaData(rs);
            maxLength = getMaxRecordLength(metaList);
            // Write Header
            writeHeaderRec(fileOut, maxLength);
            while (rs.next()) {
                // Now iterate on metaList and fetch all the column values.
                writeData(rs, metaList, fcOut);
            }
            // Write trailer
            writeTrailerRec(fileOut, cnt, maxLength);
        } catch (FileNotFoundException fnfe) {
            fnfe.printStackTrace();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        } finally {
            try {
                fileOut.close();
            } catch (IOException ioe) {
                fileOut = null;
                throw new Exception(ioe.getMessage());
            }
        }
    }
    
    private void writeData(ResultSet rs, List<TableMetaData> metaList,
            FileChannel fcOut) throws SQLException, IOException {
        StringBuilder rec = new StringBuilder();
        String lf = "\n";
        for (TableMetaData tabMeta : metaList) {
            rec.append(getFormattedString(rs, tabMeta));
        }
        rec.append(lf);
        ByteBuffer byteBuf = ByteBuffer.wrap(rec.toString()
                .getBytes("US-ASCII"));
        fcOut.write(byteBuf);
    }
    
    private String getFormattedString(ResultSet rs, TableMetaData tabMeta)
            throws SQLException, IOException {
        String colValue = null;
        // check if it is a CLOB column
        if (tabMeta.isCLOB()) {
            // Column is a CLOB, so fetch it and retrieve first clobLimit chars.
            colValue = String.format("%-" + tabMeta.getColumnSize() + "s",
                    getCLOBString(rs, tabMeta));
        } else {
            colValue = String.format("%-" + tabMeta.getColumnSize() + "s", rs
                    .getString(tabMeta.getColumnName()));
        }
        return colValue;
    

    }

    5 回复  |  直到 15 年前
        1
  •  3
  •   Community Mohan Dere    9 年前

    可能是因为你打电话的方式 prepareStatement ,请参见 this question ResultSet 将是只读的,因此只需调用

    stmt = conn.prepareStatement(query);
    
        2
  •  1
  •   Jigar Joshi    15 年前

    编辑 使用JPA将数据库表映射到类。
    现在使用Hibernate批量加载DB中的对象集合,并将其序列化为文件。

        3
  •  0
  •   Synesso    15 年前

    你的算法是这样的吗?假设DB行和文件中的行之间存在直接映射:

    // open file for writing with buffered writer.
    // execute JDBC statement
    // iterate through result set
        // convert rs to file format
        // write to file
    // close file
    // close statement/rs/connection etc
    

    尝试使用springjdbc模板来简化JDBC部分。

        4
  •  0
  •   Ashish Patil    15 年前

        5
  •  0
  •   mwhidden    15 年前

    maxRecBeforWrite的值是多少?

    也许最大记录长度的查询通过强制JDBC扫描整个结果来获取记录长度而破坏了setFetchSize?也许你可以延迟写你的头和注意最大记录大小的飞行。

    推荐文章