Java:clear() 大尺寸列表是否有助于快速垃圾收集?

问题描述

数据库 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++;
}