Java 流从 GC 角度或 GC 处理短期对象的影响

Impact of Java streams from GC perspective or handling short-lived objects by the GC

网上有一些文章提到了使用 Stream-s 而不是旧的 loop-s 的一些缺点:

但是从GC的角度来看有什么影响吗?正如我假设的那样(是否正确?),每个流调用都会在下面创建一些短暂的对象。如果使用流的特定代码片段被底层系统频繁调用,从 GC 的角度来看,它是否最终会导致一些性能问题或给 GC 带来额外的压力?或者影响很小,大多数时候可以忽略?

有没有更详细的文章?

公平地说,当 Holger 已经通过他的回答将主要思想联系起来时,给出答案非常复杂;我还是会努力的。

GC 的额外压力 - 可能是。执行 GC 循环的额外时间 - 很可能不会。可以忽略吗?我会说完全。最后,您关心的是 GC - 回收大量 space 所需的时间很短,最好是使用超小的 stop-the-world 事件。

让我们谈谈 GC 主要两个阶段的潜在开销:标记和 evacuation/realocation (Shenandoah/ZGC)。第一个 mark 阶段,其中 GC 找出什么是垃圾(通过实际识别什么是活的)。

如果 Stream 内部创建的对象不可访问,则永远不会扫描它们(此处为零开销),如果它们可访问,扫描它们的速度将非常快。故事的另一面是:当你创建一个 Object 并且 GC 可能会触及它 它在标记阶段 运行ning ,一个缓慢的路径LoadBarrier(在 Shenandoah 的情况下)将处于活动状态。这将增加几十 ns 我假设到 GC 的那个特定阶段的总时间以及 SATB 队列中的一些 space 。 Aleksey Shipilev 在一次演讲中说,他试图测量执行单个屏障的开销,但没能成功,因此他测量了 3,时间在 ns 的几十位范围内。我不知道 ZGC 的具体细节,但 LoadBarrier 也在那里。

要点是这个标记阶段是以并发方式完成的,而应用程序是 运行ning,因此您的应用程序仍然 运行 非常好。并且即使某些 GC 代码会被触发来做一些特定的工作(Load Barrier),它也会非常快并且对你完全透明。

第二阶段是"compactation",或为以后的分配做space。 GC 所做的是将活动对象从垃圾最多的区域(肯定是 Shenandoah)移动到空区域。但只有活的物体。因此,如果某个区域有 100 个对象并且只有 1 个存活,则只会移动 1 个,然后整个区域将被标记为空闲。因此,如果 Stream 实现仅生成垃圾(即:当前不存在),则可能是 "free lunch" 用于 GC,它甚至不知道它存在。

这里更好的情况是这个阶段仍然是同时完成的。要保持 "concurrency" 处于活动状态,您需要知道从 GC 周期开始到结束分配了多少。此数量是您需要在 java 流程之上拥有的最小数量 "extra" space 才能使 GC 满意。

所以总的来说,你看到的是一个非常小的影响;如果有的话。