gpt4 book ai didi

java - 在不增加堆大小的情况下修复超出 GC 开销限制的问题

转载 作者:行者123 更新时间:2023-12-01 08:45:39 25 4
gpt4 key购买 nike

我正在开发一个 Java 程序,该程序将从 Sybase 数据库获取数据,并使用 UCanAccess 将其导入 Microsoft Access 数据库。但是,我目前遇到了一个问题,收到错误“java.lang.OutOfMemoryError:超出 GC 开销限制”。

为了了解具体情况,我尝试将大约 130 万条记录导入 Access 数据库。目前,程序在导入大约 800,000 条记录后(运行时大约十分钟)以及从 Sybase 数据库检索 ResultSet 后很长时间内遇到错误。

我尝试修改堆大小,但这会导致程序显着减慢。请注意,这是一个根据需要多次运行的临时程序,因此运行时间应为几分钟或可能为几小时,而根据我的观察,增加堆大小会将运行时间增加到天。

作为引用,错误发生在主方法中调用 getRecords 的子例程中(发生此错误的确切代码行因运行而异)。我已将代码添加到下面的程序中,并对部分代码进行了一些细微的更改,例如我正在使用的确切查询以及访问数据库的用户名和密码,以免泄露敏感信息。

我可以在程序代码中更改任何内容,以减轻垃圾收集器的负载,而不会将运行时间增加到几个小时以上吗?

编辑:看来我对 Java 的默认最大堆大小有误。当我认为通过将堆大小设置为 512m 来增加堆大小时,我无意中将堆大小减少了一半。当我将堆大小设置为 2048m 时,出现 java 堆空间错误。如果可能的话,我仍然希望在不修改堆大小的情况下解决问题。

编辑2:显然,我在需要处理的一些记录方面被误导了。它的大小是我最初想象的两倍,这表明我需要彻底改变我的方法。继续并接受答案,因为该答案确实带来了很大的改进。

获取记录方法:

   public static void getRecords(SybaseDatabase sdb, AccessDatabase adb)
{
ArrayList<Record> records = new ArrayList<Record>();
StringBuffer sql = new StringBuffer();
Record currentRecord = null;
try{
Statement sybStat = sdb.connection.createStatement();
PreparedStatement resetADB = adb.connection.prepareStatement("DELETE FROM Table");
PreparedStatement accStat = adb.connection.prepareStatement("INSERT INTO Table (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
sql.append(query);//query is a placeholder, as I cannot give out the actual query to the database. I have confirmed that the query itself gives the ResultSet that I am looking for
ResultSet rs = sybStat.executeQuery(sql.toString());
resetADB.executeUpdate();
boolean nextWatch = true;
Integer i = 1;
Record r = new Record();
while(nextWatch)
{
for (int j = 0; j < 1000 && nextWatch; j++)
{
nextWatch = rs.next();

r.setColumn(i, 0);
r.setColumn(rs.getString("B"), 1);
r.setColumn(rs.getString("C"), 2);
r.setColumn(rs.getString("D"), 3);
r.setColumn(rs.getString("E"), 4);
r.setColumn(rs.getString("F"), 5);
r.setColumn(rs.getString("G"), 6);
r.setColumn(rs.getString("H"), 7);
r.setColumn(rs.getString("I"), 8);
r.setColumn(rs.getString("J"), 9);
r.setColumn(rs.getString("K"), 10);
r.setColumn(rs.getInt("L"), 11);
r.setColumn(rs.getString("M"), 12);
r.setColumn(rs.getString("N"), 13);
r.setColumn(rs.getString("O"), 14);
r.setColumn(rs.getString("P"), 15);

records.add(r);
i++;
}

for(int k = 0; k < records.size(); k++)
{
currentRecord = records.get(k);

for(int m = 0; m < currentRecord.getNumOfColumns(); m++)
{
if (currentRecord.getColumn(m) instanceof String)
{
accStat.setString(m + 1, "\"" + currentRecord.getColumn(m) + "\"");
}
else
{
accStat.setInt(m + 1, Integer.parseInt(currentRecord.getColumn(m).toString()));
}
}
accStat.addBatch();
}
accStat.executeBatch();
accStat.clearBatch();
records.clear();
}
adb.connection.commit();
}
catch(Exception e){
e.printStackTrace();
}
finally{

}
}
}

完整代码:

import java.util.*;
import java.sql.*;
import com.sybase.jdbc2.jdbc.SybDriver;//This is an external file that is used to connect to the Sybase database. I will not include the full code here for the sake of space but will provide it upon request.

public class SybaseToAccess {
public static void main(String[] args){
String accessDBPath = "C:/Users/me/Desktop/Database21.accdb";//This is a placeholder, as I cannot give out the exact file path. However, I have confirmed that it points to the correct file on the system.
String sybaseDBPath = "{sybServerName}:{sybServerPort}/{sybDatabase}";//See above comment
try{
AccessDatabase adb = new AccessDatabase(accessDBPath);
SybaseDatabase sdb = new SybaseDatabase(sybaseDBPath, "user", "password");

getRecords(sdb, adb);
}
catch(Exception e){
e.printStackTrace();
}
finally{

}
}
public static void getRecords(SybaseDatabase sdb, AccessDatabase adb)
{
ArrayList<Record> records = new ArrayList<Record>();
StringBuffer sql = new StringBuffer();
Record currentRecord = null;
try{
Statement sybStat = sdb.connection.createStatement();
PreparedStatement resetADB = adb.connection.prepareStatement("DELETE FROM Table");
PreparedStatement accStat = adb.connection.prepareStatement("INSERT INTO Table (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
sql.append(query);//query is a placeholder, as I cannot give out the actual query to the database. I have confirmed that the query itself gives the ResultSet that I am looking for
ResultSet rs = sybStat.executeQuery(sql.toString());
resetADB.executeUpdate();
boolean nextWatch = true;
Integer i = 1;
Record r = new Record();
while(nextWatch)
{
for (int j = 0; j < 1000 && nextWatch; j++)
{
nextWatch = rs.next();

r.setColumn(i, 0);
r.setColumn(rs.getString("B"), 1);
r.setColumn(rs.getString("C"), 2);
r.setColumn(rs.getString("D"), 3);
r.setColumn(rs.getString("E"), 4);
r.setColumn(rs.getString("F"), 5);
r.setColumn(rs.getString("G"), 6);
r.setColumn(rs.getString("H"), 7);
r.setColumn(rs.getString("I"), 8);
r.setColumn(rs.getString("J"), 9);
r.setColumn(rs.getString("K"), 10);
r.setColumn(rs.getInt("L"), 11);
r.setColumn(rs.getString("M"), 12);
r.setColumn(rs.getString("N"), 13);
r.setColumn(rs.getString("O"), 14);
r.setColumn(rs.getString("P"), 15);

records.add(r);
i++;
}

for(int k = 0; k < records.size(); k++)
{
currentRecord = records.get(k);

for(int m = 0; m < currentRecord.getNumOfColumns(); m++)
{
if (currentRecord.getColumn(m) instanceof String)
{
accStat.setString(m + 1, "\"" + currentRecord.getColumn(m) + "\"");
}
else
{
accStat.setInt(m + 1, Integer.parseInt(currentRecord.getColumn(m).toString()));
}
}
accStat.addBatch();
}
accStat.executeBatch();
accStat.clearBatch();
records.clear();
}
adb.connection.commit();
}
catch(Exception e){
e.printStackTrace();
}
finally{

}
}
}

class AccessDatabase{
public Connection connection = null;
public AccessDatabase(String filePath)
throws Exception
{
String dbString = null;
dbString = "jdbc:ucanaccess://" + filePath;
connection = DriverManager.getConnection(dbString);
connection.setAutoCommit(false);
}
}
class Record{
ArrayList<Object> columns;
public
Record(){
columns = new ArrayList<Object>();
columns.add("Placeholder1");
columns.add("Placeholder2");
columns.add("Placeholder3");
columns.add("Placeholder4");
columns.add("Placeholder5");
columns.add("Placeholder6");
columns.add("Placeholder7");
columns.add("Placeholder8");
columns.add("Placeholder9");
columns.add("Placeholder10");
columns.add("Placeholder11");
columns.add("Placeholder12");
columns.add("Placeholder13");
columns.add("Placeholder14");
columns.add("Placeholder15");
columns.add("Placeholder16");
}

<T> void setColumn(T input, int colNum){
columns.set(colNum, input);
}

Object getColumn(int colNum){
return columns.get(colNum);
}

int getNumOfColumns()
{
return columns.size();
}
}

class SybaseDatabase{
public Connection connection;

@SuppressWarnings("deprecation")
public SybaseDatabase(String filePath, String Username, String Password)
throws Exception
{
SybDriver driver;

try
{
driver = (SybDriver)Class.forName("com.sybase.jdbc2.jdbc.SybDriver").newInstance();
driver.setVersion(SybDriver.VERSION_6);
DriverManager.registerDriver(driver);
}
catch (Exception e)
{
e.printStackTrace(System.err);
}

connection = DriverManager.getConnection("jdbc:sybase:Tds:" + filePath, Username, Password);
}
}

最佳答案

如果您想使用更少的内存,您应该同时处理更少的行,但重用所有可以重用的对象(例如 PreparedStatement )

首先:您使用 ArrayList<>在具有固定大小的记录中。您可以只使用数组 Record[]为了那个原因。 ArrayList的原理是拥有一个动态大小的数组,这里不需要它

第二:在处理之前不要从数据库加载所有数据,加载一小部分数据并处理它,然后继续。

您可以通过提取处理某些行的代码部分并按 limiting the number of returned rows 更改查询来实现此目的。 。

现在,您加载 1000 行(从索引 0 到 999),处理并提交它们。然后加载 1000 行(从索引 1000 到 1999),处理并提交它们。然后你继续。在每包行之间,不要保留对处理过的数据(例如记录)的任何引用,以避免它们保留在内存中(就像必要时它们将被垃圾收集一样)。

如果你仍然没有足够的内存,我猜你保留了一些没有被垃圾收集的对象的引用,从而导致内存泄漏问题:你的程序在处理每个数据时需要越来越多的内存。您可以使用一些工具,例如 jvisualvm (java内提供)调查内存的使用情况

关于java - 在不增加堆大小的情况下修复超出 GC 开销限制的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44204855/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com