【问题标题】:How does Java GC deals with processed object loaded from a large Stream exceeding available heap memory?Java GC 如何处理从超过可用堆内存的大型 Stream 加载的已处理对象?
【发布时间】:2021-05-21 00:59:02
【问题描述】:

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

public interface MyJpaRepository extends JpaRepository<Foo, String> {

  Stream<Foo> findAll();
}

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

我希望按照以下方式使用流将让 JVM 通过垃圾收集处理过的对象来正确处理其堆内存,因为从数据库中加载了更多对象:

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

但事实上,这段代码会引发内存不足异常。

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

谢谢

【问题讨论】:

  • 这与垃圾收集器无关。这是关于您将那么多数据加载到内存中并使其不符合垃圾收集的条件。您的 findAll() 方法应该返回一个流,该流根据需要从数据库中获取数据(就像读取结果集时所做的那样)。
  • @ernest_k 你说得对!刚刚了解到 Postgres(在我的例子中是底层数据库)总是返回整个 ResultSet,除非另有配置。正如您所指出的,问题不在于我的findAll()method 的流代码。即使我觉得这个问题很愚蠢,我该如何配置它以按需获取数据?如果我正在考虑为HINT_FETCH_SIZE 添加QueryHint,我的方向是否正确?你能用一个可行的例子回答我的问题吗?
  • 问题在于findAll()的实现。我怀疑它是由 spring-data-jpa 给出的。该实现应该是提供不是来自已加载到内存中的数据的流的实现。例如,一个简单的实现会将数据加载到一个列表中并在上面调用.stream()。刚看了Stream rows from PostgreSQL (with fetch size),发现可能是问题所在。我对Spring不熟悉,但也许有答案。
  • 这可能是库失败的情况,您需要从中获取一些低级工件,然后您自己在代码中从那里获取它(例如,要求它给您结果设置并使用迭代技术从它构建您的流)。但是这个建议应该来自熟悉 spring-data-jpa 的人。

标签: java java-8 spring-data-jpa java-stream


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    在你的场景中,垃圾收集器不会有时间去行动和清理你的内存。让我尝试更详细地解释。当您启动 java 进程时,您配置了堆内存以及垃圾收集算法。如果您没有对其中任何一个进行微调,JVM 会理所当然地使用默认设置并继续。一旦您的进程开始分配堆,JVM 就会在内部收集统计信息并安排垃圾收集进程。但是,如果您的进程没有提供喘息的空间来决定何时以及如何收集垃圾,JVM 将抛出 Out of Memory(OOM) 错误并崩溃。

    【讨论】:

      【解决方案3】:

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

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

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

      public interface MyJpaRepository extends JpaRepository<Foo, String> {
      
        @QueryHints(
          value = {
            @QueryHint(name = HINT_FETCH_SIZE, value = "1000"),
            @QueryHint(name = HINT_CACHEABLE, value = "false"),
            @QueryHint(name = HINT_READONLY, value = "true")
        })
        Stream<Foo> findAll();
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-08-21
        • 2021-08-12
        • 1970-01-01
        • 2018-10-17
        • 2012-08-28
        • 1970-01-01
        • 2020-04-30
        • 1970-01-01
        相关资源
        最近更新 更多