Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement Stream<E> without a resource leak warning in Java

I wish to implement the Stream<E> interface (I admit, it's the unnecessarily large one) and add a builder method foo().

public MyStream<E> implements Stream<E>, ExtendedStream<E> {

    private final Stream<E> delegate;

    public MyStream(final Stream<E> stream) {
        this.delegate = stream;
    }

    // a sample Stream<E> method implementation
    @Override
    public <R> MyStream<R> map(Function<? super E, ? extends R> mapper) {
        return new MyStream<>(this.delegate.map(mapper));
    }
    // the rest in the same way (skipped)

    // a method from ExtendedStream<E>
    @Override
    public MyStream<E> foo() {
        return new MyStream(this.delegate.......);
    }  
}

So far so good.

long count = new MyStream(list.stream())
    .map(i -> i * 10)
    .foo()
    .filter(i -> i > 100)
    .count();

I have trouble with the Closeable behavior of Stream. The documentation of Stream says about closing (formatting mine):

Streams have a BaseStream.close() method and implement AutoCloseable, but nearly all stream instances do not actually need to be closed after use. Generally, only streams whose source is an IO channel (such as those returned by Files.lines(Path, Charset)) will require closing.

The only methods that close Stream are flatMap or close.

The instantiation of an object in Eclipse Oxygen is underlined with a warning:

Resource leak: '<unassigned Closeable value>' is never closed

This is not reproducible with IntelliJIdea 2018.1.5. Related questions I skimmed through are here and here. I understand the Closeable issues with File or Dictionary, however, I am stuck with Streams.

I dislike the static method MyStream.of(...) calling a private constructor workaround.

like image 681
Nikolas Charalambidis Avatar asked Dec 27 '18 18:12

Nikolas Charalambidis


People also ask

Does unclosed streams cause memory leak in Java?

Unclosed Resources Such code requires opening a stream, connection, or file inside our code. But we have to remember that we are the ones responsible not only for opening the resource but also for closing it. Otherwise, our code can leak memory, eventually leading to OutOfMemory error.

What is resource leak warning in Java?

In general, a Java memory leak happens when an application unintentionally (due to logical errors in code) holds on to object references that are no longer required. These unintentional object references prevent the built-in Java garbage collection mechanism from freeing up the memory consumed by these objects.


1 Answers

As part of work on JSR 335, the JRE libraries evolved by introducing java.util.Stream and at the same time leveraging the new concept in places like java.nio. During this time the Eclipse team was consulted by the JSR 335 expert group, to discuss the following conflict:

  • Tools like ecj want to signal when programmers forget to close a GC-resistent (GCR) resource like, e.g., a FileInputStream.

  • The library team planned to make java.util.Stream a subtype of AutoCloseable to enable usage in try-with-resource, motivated by the fact that a j.u.Stream could potentially be backed by a GCR resource. Still, the default assumption should be that instances of j.u.Stream do not require a close() call.

  • Still, certain methods in java.nio returning j.u.Stream require to be close()d.

EG and Eclipse agreed, that no easy solution could be found such that just by looking at the type of closeable any tool could precisely recognize whether closing is necessary or not. This is due to intricacies of various resources wrapping other resources at several levels. In particular the type j.u.Stream gives no indication whether or not instances are backed by GCR resources or not.

For a clean solution, it was further mentioned, that a system of type annotations (using JSR 308) would be needed to enrich the type system with the information needed for precise static analysis. To the best of my knowledge, such approach hasn't materialized until today.

As a compromise, tool implementors like Eclipse were advised to encode heuristics along the following lines:

  • Normally, all instances of type AutoCloseable should be closed.

  • The following known set of types was to be excluded from the analysis, because those typically do not require closing: java.util.Stream and {Int,Long,Double}Stream.

  • As an exception from the exception, certain Stream-returning static methods in java.nio are known to require closing.

The discussion basically happened between the following two posts on the lambda-libs-spec-observers mailing list:

  • Brian's problem statement & intermediate proposal

  • My summary of a private discussion.

So much for the history.

The discussion in 2013 did not account for custom implementations of j.u.Stream. Eclipse assumes no specific knowledge about those implementations. It would be better, if not the tool would decide a bias towards needing / not needing close(), but if the implementor (here of MyStream) would have the means to indicate, whether instances of this class require closing or not. To-date implementors have, however, no means to express this.

For lack of a complete and precise option, we could discuss extending the set of heuristics, such that not only the known set of types in the j.u.Stream family, but also all its subtypes are excluded from the analysis, and considered to be GC-friendly. Obviously, such approach would increase the risk of false negatives (bugs missed by the analysis).

Marking warnings as "potential leaks", as suggested by howlger's answer would be confusing, because in flow-analysis, the word "potential" should normally indicate a behavior that occurs on some, but not all, flows through the program.

Available options as of today are:

  • Using @SuppressWarnings("resource") wherever MyStream is used (preferable)

  • Lowering the severity of this particular problem (if use of MyStream is too wide spread for using the first option).

like image 112
Stephan Herrmann Avatar answered Oct 08 '22 21:10

Stephan Herrmann