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

Java: Does clear() the big size list help in quick garbage collection?

从数据库 1 加载 150 万条记录

从数据库 2 加载 150 万条记录

List<DannDB> dDb = fromNamedQuery(); //return em.createNamedQuery("").getResultList();
List<LannDB> lDb = fromNamedQuery();

比较它的数据。

Update/persist 进入数据库(使用 JPA)

两个小时后节目结束。

同样的迭代每三个小时发生一次,多次出现内存不足。

以下语句是否有效,对象是否超出范围?

 dDb.clear();

  or 

 dDb = null

或者我还能做什么?

问题是,默认情况下,分配的堆内存大小不会缩小(我的意思是从操作系统分配的内存大小)。如果您的 Java 应用程序一次需要 2 GB 的 RAM,它会默认保留操作系统保留的内存。

如果可以,请尝试更改应用程序的设计,不要首先将所有数据加载到内存中,而是仅加载您真正需要完成工作的内容。

如果你真的需要同时使用两个大的批次,考虑使用下面的Java命令行参数:“-XX:+UseAdaptiveSizePolicy”,这样可以缩小堆space 大量内存使用后。

您也可以通过“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++;
}

假设您的目标是减少 OOME 的发生而不是所有其他考虑...

null 分配给 List 对象将使整个列表符合垃圾回收条件。调用 clear() 将产生类似的效果,但它取决于 List 的实现。 (例如,在 ArrayList 上调用 clear() 不会释放后备数组。它只会使数组单元格为空。)

如果您可以回收 ArrayList 一个与原始列表大小大致相同的列表,则可以在列表增长时避免垃圾。 (但我们不知道这是一个 ArrayList!)

您的用例中的另一个因素是:

List<DannDB> dDb = fromNamedQuery();

(大概)无论如何都会创建一个新列表。那会使 clear() 毫无意义。 (只需将 null 分配给 dDb,或者让变量超出范围或重新分配新列表。)

最后一个问题是,可以想象该列表是可最终确定的。这可能意味着删除列表对象需要更长的时间。

总的来说,我不能说分配 null 和调用 clear() 哪个更适合内存占用。或者 其中之一 会产生显着差异。但是你没有理由不尝试这两种选择,并观察会发生什么。

我唯一可以建议的是:

  • 增加堆大小(和 RAM 占用空间)。
  • 更改应用程序,这样您就不需要在内存中保存整个数据库快照。根据比较的性质,您可以以“块”的形式进行,也可以通过流式传输记录1.

最后一个是唯一可扩展的解决方案;也就是说,这将适用于越来越多的记录。 (对处理更多记录所花费的时间取模。)


运行 System.gc() 不太可能有帮助。由于真正的问题是你得到了 OOME,任何试图通过将内存返回给 OS 来让 JVM 缩小堆的方法都会适得其反。


1 - 年龄足够大的人会记得使用磁带存储实现薪资系统的经典方法。如果您可以 select 以相同的键顺序来自两个数据源,则您可以使用经典方法来比较它们。例如,并行读取两个结果集。

在SQL的情况下,你可以得到你的两个<a href="https://docs.oracle.com/javase/8/docs/api/java/sql/ResultSet.html" rel="nofollow noreferrer">ResultSet</a>并迭代比较它们的数据。这样,您就不必首先保存所有数据。
出于演示目的,我假设您的数据如下所示:

String email1 String 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()) ...