Java 如何克服大文件写入过程中的OutOfMemory错误
我正在用java编写一个完整的数据库提取程序。数据库是Oracle,它是巨大的。有些表格有2.6亿条记录。该程序应以特定格式为每个表创建一个文件,因此不能使用Oracle datapump等。此外,一些公司的安全策略不允许编写PL/SQL过程来在DB server上为此需求创建文件。我必须使用Java和JDBC 我面临的问题是,由于表中某些部分的文件很大(~30GB),即使使用20GB的Java堆,我几乎每次都会耗尽内存。在创建文件的过程中,当文件大小超过堆大小时,即使使用最激进的GC策略之一,该过程似乎也会挂起。例如,如果文件大小大于20GB,而堆大小为20GB,则一旦堆利用率达到最大堆大小,其每分钟写入2MB的速度就会减慢,以这种速度,将需要数月时间才能完全提取 我正在寻找克服这个问题的方法。任何帮助都将不胜感激 以下是我的系统配置的一些详细信息: Java-JDK1.6.0_14 系统配置-运行在4 X Intel Xeon E7450(6核)上的RH Enterprise Linux(2.6.18)@2.39GH 内存-32GB oracle11g数据库 文件wirting部分代码如下所示:Java 如何克服大文件写入过程中的OutOfMemory错误,java,jdbc,out-of-memory,Java,Jdbc,Out Of Memory,我正在用java编写一个完整的数据库提取程序。数据库是Oracle,它是巨大的。有些表格有2.6亿条记录。该程序应以特定格式为每个表创建一个文件,因此不能使用Oracle datapump等。此外,一些公司的安全策略不允许编写PL/SQL过程来在DB server上为此需求创建文件。我必须使用Java和JDBC 我面临的问题是,由于表中某些部分的文件很大(~30GB),即使使用20GB的Java堆,我几乎每次都会耗尽内存。在创建文件的过程中,当文件大小超过堆大小时,即使使用最激进的GC策略之一,
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;
private void runQuery(连接连接、字符串查询、字符串文件路径、,
字符串文件名)引发SQLException,异常{
PreparedStatement stmt=null;
结果集rs=null;
试一试{
stmt=conn.prepareStatement(查询,
ResultSet.TYPE\u SCROLL\u不敏感,
结果集CONCUR_只读);
stmt.setFetchSize(maxRecBeforWrite);
rs=stmt.executeQuery();
//将查询结果写入文件
WriteDataofile(rs,文件路径+“/”+文件名,getRecordCount(
查询(康涅狄格州);
}捕获(SQLException sqle){
printStackTrace();
}最后{
试一试{
rs.close();
stmt.close();
}catch(SQLException-ex){
掷骰子;
}
}
}
私有void writedataofile(结果集rs、字符串tempFile、字符串cnt)
抛出SQLException,异常{
FileOutputStream fileOut=null;
int maxLength=0;
试一试{
fileOut=新的FileOutputStream(tempFile,true);
FileChannel fcOut=fileOut.getChannel();
列表metaList=getMetaData(rs);
maxLength=getMaxRecordLength(metaList);
//写头
writeHeaderRec(文件输出,最大长度);
while(rs.next()){
//现在迭代metaList并获取所有列值。
写入数据(rs、metaList、fcOut);
}
//写预告片
writeTrailerRec(文件输出、cnt、maxLength);
}捕获(FileNotFoundException fnfe){
fnfe.printStackTrace();
}捕获(ioe异常ioe){
ioe.printStackTrace();
}最后{
试一试{
fileOut.close();
}捕获(ioe异常ioe){
fileOut=null;
抛出新异常(ioe.getMessage());
}
}
}
私有void writeData(结果集rs、列表金属列表、,
FileChannel(fcOut)抛出SQLException、IOException{
StringBuilder rec=新的StringBuilder();
字符串lf=“\n”;
for(TableMetaData tabMeta:metaList){
rec.append(getFormattedString(rs,tabMeta));
}
记录附加(lf);
ByteBuffer byteBuf=ByteBuffer.wrap(rec.toString())
.getBytes(“US-ASCII”);
fcOut.write(byteBuf);
}
私有字符串getFormattedString(结果集rs,TableMetaData选项卡Meta)
抛出SQLException,IOException{
字符串colValue=null;
//检查它是否是CLOB列
if(tabMeta.isCLOB()){
//列是CLOB,所以获取它并检索第一个clobLimit字符。
colValue=String.format(“%-”+tabMeta.getColumnSize()+“s”,
getCLOBString(rs,tabMeta));
}否则{
colValue=String.format(“%-”+tabMeta.getColumnSize()+“s”,rs
.getString(tabMeta.getColumnName());
}
返回colValue;
}编辑:
使用JPA将数据库表映射到类。现在,使用Hibernate以一定的允许大小批量加载DB中的对象集合,并将其序列化为文件。您的算法如下所示吗?这是假设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部分。我相信这在默认的32MB java堆上是可能的。只需获取每一行,将数据保存到文件流中,完成后刷新并关闭。这可能是由于您调用的方式,请参阅以了解类似的问题。您不需要可滚动性,
ResultSet
将是只读的,因此只需调用
stmt = conn.prepareStatement(query);
maxRecBeforWrite的值是多少
通过强制JDBC扫描整个结果中的记录长度,最大记录长度的查询可能会破坏setFetchSize?也许您可以延迟写入头并动态记录最大记录大小。您没有提供任何代码,因此很难准确说出您做错了什么……如果您将记录流式传输到文件,您应该不会有问题,在将整个2GB表写入文件之前,没有理由将其保留在内存中。还要检查您的代码中如何使用字符串。没有任何代码,很难提出任何解决方案。是的,我们需要一些代码来审查。伪代码(如有必要)。@Amit,我可能会分离出有问题的代码,尝试在不写入文件的情况下遍历表,您是否仍在耗尽内存?您确定文件大小不会影响堆大小的使用吗。我正按照你说的去做。我正在一条记录一条记录地写。Spring JDBC如何帮助减少巨大的文件写入过程所导致的内存使用?使用
SimpleJdbcTemplate
w