为啥在执行 Java Stream 终端操作时对象没有被垃圾收集?
Posted
技术标签:
【中文标题】为啥在执行 Java Stream 终端操作时对象没有被垃圾收集?【英文标题】:Why objects not being garbage collected while executing Java Stream terminal operation?为什么在执行 Java Stream 终端操作时对象没有被垃圾收集? 【发布时间】:2020-11-12 20:57:59 【问题描述】:我试图了解什么持有对对象的引用,以便在执行 Java Stream 终端操作时它们不符合垃圾收集条件?
这是我的测试代码
import java.util.stream.IntStream;
import java.util.stream.Stream;
class Scratch
public static void main(String[] args)
Stream<LargeObject> objectStream = IntStream.rangeClosed(0, 1000000).mapToObj(LargeObject::new);
objectStream.peek(obj ->
try
Thread.sleep(1);
if (obj.i % 100 == 0)
System.out.println("Processed: " + obj.i);
if (obj.i % 10000 == 0)
System.out.println("Calling GC");
System.gc();
catch (InterruptedException e)
e.printStackTrace();
)
.map(largeObject -> new Object())
.count();
private static class LargeObject
private final int i;
private final byte[] alloc = new byte[1024];
private LargeObject(int i)
this.i = i;
@Override
protected void finalize() throws Throwable
super.finalize();
System.out.println("" + i + " collected");
LargetObject 的 finalize 方法永远不会被调用。
我的想法是,一旦 .map(largeObject -> new Object()) 被执行,那么没有任何东西拥有对 LargeObject 的强引用,它就可以进行垃圾回收了。
为什么这不会发生?也许真的可以做点什么?
【问题讨论】:
这很可能只是finalize()
和 System.gc()
实际上不会让你观察到这种行为。
我在 List 中使用了相同的“peek”函数代码,它是 List.iterator(),在迭代时删除对象。 finalize() 方法被调用
您是否真的使用分析器监控 RAM 使用情况?
它很稳定(~1.3G),不会下降。我在这个测试中使用了 -Xmx1500m。
【参考方案1】:
这与终结和gc
无关。你应该提到你在java-9
或更高版本(我假设是这样,但应该是这样)。我会简化一下:
long howMany = List.of(1, 2, 3)
.stream()
.map(x ->
System.out.println("mapping = " + x);
return x;
)
.count();
System.out.println(howMany);
由于初始 Stream 具有已知大小并且没有更改,因此您的 peek
和 map
根本不会执行(因此不会产生垃圾)。看到这个:
public static void main(String[] args)
List<Integer> list = List.of(1, 2, 3);
Stream<Integer> one = isSized(list.stream());
Stream<Integer> two = isSized(one.map(x ->
System.out.println("mapping = " + x);
return x;
));
long count = isSized(two).count();
System.out.println(count);
private static Stream<Integer> isSized(Stream<Integer> stream)
Spliterator<Integer> sp = stream.spliterator();
System.out.println(sp.hasCharacteristics(Spliterator.SIZED));
return StreamSupport.stream(sp, stream.isParallel());
用java-8编译同样的代码,你会看到不同的画面。
【讨论】:
我的错,我发布的代码版本与我使用的代码版本似乎有点不同。我的版本是 Collectors.toList(),也有这些行:List<LargeObject> objects = IntStream.rangeClosed(0, 1000000).mapToObj(LargeObject::new).collect(Collectors.toList()); Stream<LargeObject> objectStream = objects.stream(); objects = null;
我发布的代码的答案是正确的。以上是关于为啥在执行 Java Stream 终端操作时对象没有被垃圾收集?的主要内容,如果未能解决你的问题,请参考以下文章
为啥具有短路操作的并行 Java Stream 会评估 Stream 的所有元素,而顺序 Stream 不会?
Java:为啥在使用 Stream+Iterator 时不会发生 ConcurrentModificationException?