Home > OS >  Can the Java stream consume / destroy the source collection at the same time?
Can the Java stream consume / destroy the source collection at the same time?

Time:12-09

Sample:

public class Dummy {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3);
        List<Integer> squareNumbers = numbers.stream().map(x -> x * x).collect(Collectors.toList());
        // I don't need `numbers` anymore.
        System.out.println(squareNumbers);
    }
}

After creating squareNumbers collection, I don't need numbers collection anymore. I want to release the numbers collection elements as soon as possible (to free the memory). Can I achieve this by using Java streams?

CodePudding user response:

That is not how Java garbage collection works. Streams do not 'consume' or 'destroy' collections. As long as there is a reference (in this case the local variable numbers), the object will not get garbage collected. In other words, the object will become eligible for GC after the methods ends (though potentially an intelligent JVM might allow for it to be collected when the local variable is no longer used for the remainder of the method).

In the method as shown, you really don't need to do anything, as the method ends soon after the last use of numbers.

Also, garbage collection in Java doesn't run immediately when an object becomes eligible for garbage collection, it might take some time before it will get collected (depending on a multitude of factors, including the actual garbage collector and its configuration).

You could do one of the following:

  1. Inline the variable numbers:

    List<Integer> squareNumbers = Arrays.asList(1, 2, 3).stream().map(x -> x * x).collect(Collectors.toList());
    
  2. Explicitly null the local variable after its last use.

However, neither is likely to have a significant benefit, unless the remainder of the method is very long-running. And doing this (and especially option 2) without a very good reason leads to hard to read code, and it will likely not help at all if the JVM is smart enough or if the method ends soon after. Option 2 might even actively interfere with some optimizations (though I'm guessing here).

If memory is really a concern, it would be better to remove the use of boxed integers, and use primitive arrays (i.e. int[]), a normal for-loop, and - potentially - reuse the same array.

For example:

int[] numbers = { 1, 2, 3 };
for (int idx = 0; idx < numbers.length; idx  ) {
    numbers[idx] = numbers[idx] * numbers[idx];
}
System.out.println(Arrays.toString(numbers));

Using IntStream.iterate with a limit as suggested by MC Emperor (or IntStream.of if the input isn't contiguous) can also reduce memory consumption. However, keep in mind that streams are complex, stateful objects, which comes with their own memory overhead.

CodePudding user response:

You generally should not care about this. numbers is a local variable, and as soon as it goes out of scope, it is eligible for garbage collection. The JVM will release the used memory if it decides that it is necessary at that time.

If you really worry about memory, then don't create a List in the first place. In your case, you could use a generator instead of a list:

IntStream.iterate(1, i -> i   1)
    .limit(3)

Then you could directly operate on the created stream1:

    .map(n -> n * n)
    .boxed()
    .collect(Collectors.toList());

1 I created an IntStream instead of a Stream to avoid boxing/unboxing overhead, as suggested by Mark Rotteveel in the comments. boxed() is used to convert to a Stream<Integer>.

  • Related