问题描述
从数据库 1 加载 150 万条记录
从数据库 2 加载 150 万条记录
Developer_Name
比较它的数据。
更新/持久化到数据库中(使用 JPA)
程序在两个小时后结束。
同样的迭代每三小时发生一次,很多时候会出现内存不足。
以下语句是否有效,对象是否超出范围?
List<DannDB> dDb = fromNamedQuery(); //return em.createNamedQuery("").getResultList();
List<LannDB> lDb = fromNamedQuery();
或者我还能做什么?
解决方法
假设您的目标是减少 OOME 的发生,而不是所有其他考虑因素......
将 null
分配给 List
对象将使整个列表有资格进行垃圾回收。调用 clear()
会产生类似的效果,但它取决于 List
的实现。 (例如,在 clear()
上调用 ArrayList
不会释放后备数组。它只是将数组单元格清空。)
如果您可以为与原始大小大致相同的列表回收 ArrayList
,则可以在增加列表的同时避免垃圾。 (但我们不知道这是一个 ArrayList
!)
您的用例中的另一个因素是:
List<DannDB> dDb = fromNamedQuery();
(大概)无论如何都会创建一个新列表。这会使 clear()
变得毫无意义。 (只需将 null
分配给 dDb
,或者让变量超出范围或重新分配新列表。)
最后一个问题是,可以想象该列表是可确定的。这可能意味着删除列表对象需要更长的时间。
总的来说,我不能说分配 null
和调用 clear()
哪个对内存占用更好。或者其中一个会产生重大影响。但是没有理由不能尝试两种选择,然后观察会发生什么。
我唯一能建议的其他事情是:
- 增加堆大小(和 RAM 占用空间)。
- 更改应用程序,以便您无需将整个数据库快照保存在内存中。根据比较的性质,您可以“分块”进行,也可以流式传输记录1。
最后一个是唯一可扩展的解决方案;即,这将适用于越来越多的记录。 (以处理更多记录所需的时间为模。)
运行 System.gc()
不太可能有帮助。由于真正的问题是您得到了 OOME,任何试图通过将内存返还给操作系统来让 JVM 缩小堆的行为都会适得其反。
1 - 那些年纪够大的人会记得实施带有磁带存储的工资系统的经典方法。如果您可以从两个数据源中以相同的键顺序进行选择,您也许可以使用经典方法来比较它们。例如,并行读取两个结果集。
,在 SQL 的情况下,您可以获取两个 ResultSet
并迭代比较它们的数据。这样,您就不必首先保存所有数据。
出于演示目的,我假设您的数据如下所示:
字符串 email1 | 字符串 email2 | int someInt |
---|---|---|
abc@def.ghi | jkl@mno.pqr | 1234567 |
xyz@gmail.com | 8901234 |
要检测此数据库的两个 ResultSet
之间的差异:
boolean equals(ResultSet a,ResultSet b) {
while(a.next() && b.next()) {
String aEmail1 = a.getString(1);
String bEmail1 = b.getString(1);
if(!aEmail1.equals(bEmail1)) return false;
String aEmail2 = a.getString(2);
String bEmail2 = b.getString(2);
if(!aEmail2.equals(bEmail2)) return false;
int aSomeInt = a.getInt(3);
int bSomeInt = b.getInt(3);
if(aSomeInt!=bSomeInt) return false;
if(a.isLast()!=b.isLast())
throw new IllegalArgumentException(
"ResultSets have different amounts of rows!"
);
}
return true;
}
将ResultSet oldData
的内容(也是其对应的数据库连接)设置为ResultSet newData
:
void updateA(ResultSet oldData,ResultSet newData) {
while(oldData.next() && newData.next()) {
String newEmail1 = newData.getString(1);
oldData.updateString(1,newEmail1);
String newEmail2 = newData.getString(2);
oldData.updateString(2,newEmail2);
int newSomeInt = newData.getInt(3);
oldData.updateInt(3,newSomeInt);
if(oldData.isLast()!=newData.isLast())
throw new IllegalArgumentException(
"ResultSets have different amounts of rows!"
);
}
}
如果您不关心两个集合的行数不同,您当然可以省略 if(a.isLast()!=newData.isLast)) ...
和 if(oldData.isLast()!=newData.isLast()) ...
。
问题是,默认情况下,一旦分配的堆内存大小不会缩小(我的意思是从操作系统分配的内存大小)。如果您的 Java 应用程序曾经需要 2 GB 的 RAM,它会默认为操作系统保留该内存。
如果可以,请尝试更改应用程序的设计,不要首先将所有数据加载到内存中,而只加载完成工作真正需要的数据。
如果您真的需要同时进行两个大批量处理,请考虑使用以下 Java 命令行参数:“-XX:+UseAdaptiveSizePolicy”,这样可以在大量内存使用后缩小堆空间。
>您也可以通过“System.gc();”调用垃圾收集器,但是 a) 在没有建议的命令行参数的情况下不会缩小分配的堆内存,并且 b) 实际上,您不应该考虑这一点。 Java 会随着时间自行运行。
编辑:稍微改进了我的第一个解释。
,最适合内存使用的是列表不会超出范围。所以最好(在内存方面)一个一个地修改内容,只保留一个临时条目对象而不是整个其他列表。
因此您可以创建一个 getNextFromNamedQuery()
和 hasNextInNamedQuery()
方法并set 当前索引处的数据。
例如:
int i=0;
while(hasNextInNamedQuery()) {
if(dDb.size()<=i) dDb.add(getNextFromQuery());
else dDb.set(i,getNextFromQuery());
i++;
}