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:
Activity instance is createdActivity 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 :)
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:
To create Observable we are using fromCallable (just for example purposes)
As a supplier parameter we are passing our AIC
This supplier is then passed to ObservableFromCallable
The supplier (our AIC) is stored as an instance field of ObservableFromCallable
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
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: