'Do I need to unsubscribe from an Observable if the Observable is finished with?

Let us say I have an Observable (hot, doesn't complete), and I subscribe to it. Ordinarily when I am finished with the Subscription I have to unsubscribe it to prevent memory leaks.

let subject$ = new Subject();

const sub = subject$.subscribe(...);
...
// Need to call sub.unsubscribe() when we are finished
sub.unsubscribe();
sub = null;

But if instead of just being finished with the Subscription I am also finished with the Observable (Subject) and I remove all reference to both, do I need to call unsubscribe method?

let subject$ = new Subject();

const sub = subject$.subscribe(...);
...
sub = null;
subject$=null;
// Assume I have no other references to these
// Do I need to call sub.unsubscribe() in this case?

My logic is telling my I don't because both the Subject and the Subscription are now eligible for garbage collection, and will be destroyed, even though they reference each other. Or is there some hidden reference I don't know about?

Don't worry about the difference between using unsubscribe, takeUntil or other mechanisms.



Solution 1:[1]

In the case of a let subject$ = new Subject(); clearing the references to the Subject and the Subscription will be enough, everything will be garbage collected after that.

The risk of a memory leak gets real when you are subscribing to the Subject in an object, and you don't unsubscribe from the Subject before clearing all the references on the object. In that case, the whole object will remain active and won't be garbage collectable.

Let's take this example:

class BigClass {
    constructor(observable) {
        this.bigArray = new Array(9999999).fill(0);
        observable.subscribe(x => this.result = x);
    }
    //...
}

let subject = new rxjs.Subject();
let bigObject = new BigClass(subject);
let bigObject1 = new BigClass(subject);
let bigObject2 = new BigClass(subject);
let bigObject3 = new BigClass(subject);

bigObject = null;
bigObject1 = null;
bigObject2 = null;
bigObject3 = null;

In this example, when clearing all the references on bigObject, the subject still has a reference on the x => this.result = x callback which has a reference on bigObject, making it uncollectable as a whole.

Either by unsubscribing, or clearing the subject, this will break the references chain that keeps bigObject alive, and it will be eligible for garbage collection.

To observe the behavior by yourself, you can copy the content of this file https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.min.js in your console, then copy paste the example code. You will notice a memory increase in the task manager. When creating a heap dump in the Memory tab in the developer tools, you'll be able to find the 4 objects by typing BigClass in the search field.

After that, type subject = null; in the console, then create a new heap dump. You'll notice that the 4 objects have disappeared.

As a conclusion, as long as an Observable is destroyed, these is no real risk of a memory leak because all of the subscriptions will also be destroyed. The risky Observables are those who are permanent (e.g: attached to a global DOM event with fromEvent), and with callbacks referring to objects that need to be destroyed.

Solution 2:[2]

No, you don't need to

For memory usage there's no difference.

When you call sub.unsubscribe(); the only thing that RXJS does is to set the observers to null, here you can see the original RXJS unsubscribe code:

  unsubscribe() {
    this.isStopped = true;
    this.closed = true;
    this.observers = null!;
  }

The flags are used only for further validations.

However i would recommend you to stick with the unsubscribe way, that would be because you never know what RXJS will add in future releases. For example there's the possibility that they add a new feature, something like this:

  unsubscribe() {
    this.isStopped = true;
    this.closed = true;
    this.observers = null!;
    if (this.coolNewFeature) {
        this.coolNewFeature.unsubscribe()
    }
  }

In that scenario you'r approach of just doing subject = null; will cause memory-leaks (because the coolNewFeature might have a reference somewhere else).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2