Rxjava: Anonymous inner classes and memory leaks

Created on 17 Feb 2017  Â·  6Comments  Â·  Source: ReactiveX/RxJava

Hi,

I saw a lot of RxJava tutorials (for both 1.x and 2.x versions) on the internet where the Java anonymous inner classes (AIC) are used to construct Observable chains. Some guides (like this one) advise to use Retrolambda instead, but Retrolamda lambdas are converted to AIC anyway.

Now consider the following example:

public class SomeActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getMyObservable()
        .map(new Function<InputType, OutputType>() {
            @Override
            public OutputType apply(final InputType input) {
                ...
                return someOutput;
            }
        })
        .subscribeOn(Schedulers.computation())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new BiConsumer<ResultType, Throwable>() {
             @Override
             public void accept(@NonNull ResultType result, @NonNull Throwable throwable) throws Exception {
                // Handle result here...
             }
         });
    }
 }

Imagine MyObservable internally starts some long-running async task. As you can see, there are two AIC in the example above which hold implicit references to the enclosing Activity.

And what would happen on a configuration change:

  • A new Activity instance is created
  • The old Activity is not needed anymore and, therefore, it must be garbage-collected. But it can't! Since the long-running task is still in progress the old Activity instance will be alive because of the implicit references that were passed to map and subscribe.

This popular tutorial suggests to overcome this issue by unsubscribing in onPause or some other lifecycle event. But, can you tell me, does unsubscribing really destroy somehow all these implicit references that were passed with AIC?

I've checked RxJava operators source code and, as far as I can see, there is no such functionality there (correct me if I'm wrong). For example, let's take a closer look at ObservableFromCallable:

@Override
    public void subscribeActual(Observer<? super T> s) {
        DeferredScalarDisposable<T> d = new DeferredScalarDisposable<T>(s);
        s.onSubscribe(d);
        if (d.isDisposed()) {
            return;
        }
        T value;
        try {
            value = ObjectHelper.requireNonNull(callable.call(), "Callable returned null");
            ...

Here callable.call() may represent a long-running operation. And while this operation is executing the reference to the Observer s will be alive so it cannot be garbage-collected, right? Can you explain how exactly unsubscribing will kill the reference to the observer?

It seems that everyone is happy with AIC and Retrolambda and I feel like I'm missing something :)

2.x Android Question

Most helpful comment

Stateless lambdas do not capture the enclosing instance even when
Retrolambda is in use.

On Fri, Feb 17, 2017, 9:48 AM Stan Mots notifications@github.com wrote:

Thanks. So you are confirming that I can freely use AIC and there will be
no memory leaks as long as I'm clearing CompositeDisposable in onPause?

What I don't get is how exactly this "root" of the chain becomes broken?
As I discussed above, in the case of ObservableFromCallable what would
stop the callable.call() invocation taking into account that it is some
long-running operation? Is clear() method from CompositeDisposable
supposed to do that?

Here is how I'm analysing the code:

1.

To create Observable we are using fromCallable
https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/Observable.java#L1582
(just for example purposes)
2.

As a supplier parameter we are passing our AIC
3.

This supplier is then passed
https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/Observable.java#L1584
to ObservableFromCallable
4.

The supplier (our AIC) is stored
https://github.com/ReactiveX/RxJava/blob/d3455d0c9d57d522c31b5c25af83e8f2b8df12b6/src/main/java/io/reactivex/internal/operators/observable/ObservableFromCallable.java#L29
as an instance field of ObservableFromCallable
5.

When we are subscribing to the returned Observable there will be blocking
invocation
https://github.com/ReactiveX/RxJava/blob/d3455d0c9d57d522c31b5c25af83e8f2b8df12b6/src/main/java/io/reactivex/internal/operators/observable/ObservableFromCallable.java#L42.
Since it is blocking the callable (which is our AIC in turn) will live
as long as this operation is executing. And since AIC holds an implicit
reference to our Activity it won't be GCd too.

And here is the question: when CompositeDisposable is cleared will
callable.call() be stopped somehow?

I'm sorry to bother you, I just wanna fully understand how rx operations
work to prevent memory leaks.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/ReactiveX/RxJava/issues/5107#issuecomment-280669390,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAEEEROlPwUv5sFQ0-p61MsRwf24hsyfks5rdbNFgaJpZM4MEP5I
.

All 6 comments

You are supposed to add the returned Disposable to a CompositeDisposable and otherwise not reference it. When onPause happens, the CompositeDisposable is cleared and forgets the Disposable. Even though operators reference their upstream via field, the "root" of the chain is now broken and the whole chain can get GCd (eventually).

Thanks. So you are confirming that I can freely use AIC and there will be no memory leaks as long as I'm clearing CompositeDisposable in onPause?

What I don't get is how exactly this "root" of the chain becomes broken? As I discussed above, in the case of ObservableFromCallable what would stop the callable.call() invocation taking into account that it is some long-running operation? Is clear() method from CompositeDisposable supposed to do that?

Here is how I'm analysing the code:

  1. To create Observable we are using fromCallable (just for example purposes)

  2. As a supplier parameter we are passing our AIC

  3. This supplier is then passed to ObservableFromCallable

  4. The supplier (our AIC) is stored as an instance field of ObservableFromCallable

  5. When we are subscribing to the returned Observable there will be blocking invocation. Since it is blocking the callable (which is our AIC in turn) will live as long as this operation is executing. And since AIC holds an implicit reference to our Activity it won't be GCd too.

And here is the question: when CompositeDisposable is cleared will callable.call() be stopped somehow?

I'm sorry to bother you, I just wanna fully understand how rx operations work to prevent memory leaks.

Stateless lambdas do not capture the enclosing instance even when
Retrolambda is in use.

On Fri, Feb 17, 2017, 9:48 AM Stan Mots notifications@github.com wrote:

Thanks. So you are confirming that I can freely use AIC and there will be
no memory leaks as long as I'm clearing CompositeDisposable in onPause?

What I don't get is how exactly this "root" of the chain becomes broken?
As I discussed above, in the case of ObservableFromCallable what would
stop the callable.call() invocation taking into account that it is some
long-running operation? Is clear() method from CompositeDisposable
supposed to do that?

Here is how I'm analysing the code:

1.

To create Observable we are using fromCallable
https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/Observable.java#L1582
(just for example purposes)
2.

As a supplier parameter we are passing our AIC
3.

This supplier is then passed
https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/Observable.java#L1584
to ObservableFromCallable
4.

The supplier (our AIC) is stored
https://github.com/ReactiveX/RxJava/blob/d3455d0c9d57d522c31b5c25af83e8f2b8df12b6/src/main/java/io/reactivex/internal/operators/observable/ObservableFromCallable.java#L29
as an instance field of ObservableFromCallable
5.

When we are subscribing to the returned Observable there will be blocking
invocation
https://github.com/ReactiveX/RxJava/blob/d3455d0c9d57d522c31b5c25af83e8f2b8df12b6/src/main/java/io/reactivex/internal/operators/observable/ObservableFromCallable.java#L42.
Since it is blocking the callable (which is our AIC in turn) will live
as long as this operation is executing. And since AIC holds an implicit
reference to our Activity it won't be GCd too.

And here is the question: when CompositeDisposable is cleared will
callable.call() be stopped somehow?

I'm sorry to bother you, I just wanna fully understand how rx operations
work to prevent memory leaks.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/ReactiveX/RxJava/issues/5107#issuecomment-280669390,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAEEEROlPwUv5sFQ0-p61MsRwf24hsyfks5rdbNFgaJpZM4MEP5I
.

Stateless lambdas do not capture the enclosing instance even when
Retrolambda is in use.

Wow, thanks. I didn't know that Retrolambda has this feature. That's cool. So, it seems I can fully replace all these anonymous classes with retrolambdas and they will have the same behavior as real Java 8 lambdas, right?

Correct

On Fri, Feb 17, 2017, 10:03 AM Stan Mots notifications@github.com wrote:

Stateless lambdas do not capture the enclosing instance even when
Retrolambda is in use.

Wow, thanks. I didn't know that Retrolambda has this feature. That's cool.
So, it seems I can fully replace all these anonymous classes with
retrolambdas and they will have the same behavior as real Java 8 lambdas,
right?

—
You are receiving this because you commented.

Reply to this email directly, view it on GitHub
https://github.com/ReactiveX/RxJava/issues/5107#issuecomment-280673523,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAEEEffU3f29-874MVPKN_YbABX22vFZks5rdbbRgaJpZM4MEP5I
.

Ok, thanks

Was this page helpful?
0 / 5 - 0 ratings