Java GC如何处理从超出可用堆内存的大型Stream加载的已处理对象?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (3)
  • 关注 (0)
  • 查看 (7)
EN

假设我有一个从数据库加载的对象流(使用Spring Data JPA,如下所示)

public interface MyJpaRepository extends JpaRepository {

  Stream findAll();
}

假设有数百万个Foo对象存储在我的数据库中,使用的GB比我的最大堆内存大小大得多。

我期望通过如下方式使用流,当从数据库加载更多的对象时,JVM可以通过垃圾收集处理过的对象来正确地处理它的堆内存:

try (Stream fooStream =
    myJpaRepository.findAll()) {
  fooStream.forEach(entity -> logger.info("Hello !"));
}

但实际上,这段代码会抛出内存不足异常。

  • 在这种情况下,垃圾收集器是如何工作的?
  • 使用forEach消费这个流需要JVM将流中的数据完全加载到内存中(根据我的理解)?

谢谢你

Jeep87cJeep87c提问于
EN
Jeep87c回答于

@ernest_K在他的评论中是100%的,这个问题与流无关。正如@avishek-bhattacharya解释的那样:

流不存储数据;相反,它们提供来自集合、数组或IO通道等源的数据。通常,这些都是懒惰的评估。

事实上,Postgres (在我的例子中是底层DB )总是返回整个ResultSet,除非另行配置(对Postgres也是如此)。要将其配置为使用数据库游标,您需要执行以下操作:

public interface MyJpaRepository extends JpaRepository {

  @QueryHints(
    value = {
      @QueryHint(name = HINT_FETCH_SIZE, value = "1000"),
      @QueryHint(name = HINT_CACHEABLE, value = "false"),
      @QueryHint(name = HINT_READONLY, value = "true")
  })
  Stream findAll();
}
EN
Steephen回答于

在您的场景中,垃圾收集器将不会获得时间来采取行动并清理您的内存。让我试着解释更多细节。在启动java进程时,您配置了堆内存和垃圾收集算法。如果您没有对它们中的任何一个进行微调,JVM就会认为默认设置是理所当然的,然后继续执行。一旦您的进程开始分配堆,JVM就会在内部收集统计信息并调度垃圾收集过程。但是,如果您的进程没有提供足够的空间来决定何时以及如何收集垃圾,JVM将抛出内存溢出(OOM)错误并崩溃,正如您所观察到的那样。

EN
Avishek Bhattacharya回答于

Java Stream不会从底层数据库获取所有数据。流不存储数据;相反,它们提供来自集合、数组或IO通道等源的数据。通常,这些都是懒惰的评估。所以,当looger.info在每个实体上调用时,stream将从基础数据存储区获取数据并应用命令。因为流只提供了一个迭代器,所以它只需要获取迭代中的下一个数据,而不是整个集合。一旦对其应用了lambda函数,GC就会删除所获取的数据。

EN

可能回答问题的人

  • 云存储

    腾讯云 · 云存储 (已认证)

    75 粉丝0 提问0 回答
  • galen

    腾讯 · 高级工程师 (已认证)

    10 粉丝0 提问41 回答
  • Jinqn

    腾讯 · 高级工程师 (已认证)

    28 粉丝0 提问65 回答
  • 腾讯云技术服务团队

    腾讯云 · 技术服务团队 (已认证)

    48 粉丝0 提问8 回答
  • elliswu

    腾讯计算机系统有限公司 · 高级工程师 (已认证)

    6 粉丝0 提问0 回答
  • 杨泽华

    腾讯云 · 高级解决方案架构师 (已认证)

    26 粉丝0 提问0 回答

扫码关注云+社区

领取腾讯云代金券