facebook / memlab

A framework for finding JavaScript memory leaks and analyzing heap snapshots

Home Page:https://facebook.github.io/memlab/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Question: Any idea about ignoring weak reference and circular reference in heap snapshot?

LZTWilliam opened this issue · comments

I've been locating memory leak problem for days. Memory tool in Chrome DevTools is good, but in my case, not enough.
The framework (Vue3 BTW) creates a WeakMap and store it in a global handle, stores a mapping from object to its proxy. And the map will never be gc'd (by design).
When I start locating the leak point, almost every object is referenced as a key by this WeakMap. And in my opinion, this is a weak reference, which shouldn't prevent object from being gc'd. So I just want the tool to ignore these weak references.
I also found chromium issue requiring this feature, unfortunately it has been closed for several years.
https://bugs.chromium.org/p/chromium/issues/detail?id=1103770

And finally I found memlab, after using "memlab find-leaks" to analyze heap snapshot, I found this. WeakMap still on every reference chain of leaks.

--Similar leaks in this run: 1065--
--Retained size of leaked objects: 273.8KB--
[<synthetic>] (synthetic) @1 [52.3MB]
  --2 (shortcut)--->  [Window / http://localhost:8082] (object) @842321 [43.7KB]
  --__VUE_INSTANCE_SETTERS__ (property)--->  [Array] (object) @914661 [164 bytes]
  --0 (element)--->  [<closure>] (closure) @1802037 [72 bytes]
  --context (internal)--->  [<function scope>] (object) @921573 [663.7KB]
  --de (variable)--->  [WeakMap] (object) @807167 [131.1KB]
  --table (internal)--->  [<array>] (array) @1827325 [131KB]

  --17779 / part of key (Object @2516105) -> value (system / JSProxy @2480815) pair in WeakMap (table @1827325) (internal)--->  [system / JSProxy] (hidden) @2480815 [704 bytes]
  ↑↑↑↑↑↑↑ THIS IS A WEAK REFERENCE ↑↑↑↑↑↑↑

  --1 (hidden)--->  [Object] (object) @2516105 [688 bytes]
  --emit (property)--->  [native_bind] (closure) @2487389 [36 bytes]
  --bound_argument_0 (shortcut)--->  [Object] (object) @2474229 [3.1KB]
  --subTree (property)--->  [Object] (object) @2487375 [544 bytes]
  --el (property)--->  [Detached HTMLFormElement] (native) @2460907 [1.3KB]
  --7 (element)--->  [Detached InternalNode] (native) @355282 [0 byte]
  --3 (element)--->  [Detached HTMLButtonElement] (native) @2462367 [504 bytes]
  --7 (element)--->  [Detached HTMLElement] (native) @2462399 [200 bytes]
  --6 (element)--->  [Detached Text] (native) @2463177 [108 bytes]
  --4 (element)--->  [Detached SVGSVGElement] (native) @2463171 [768 bytes]
  --8 (element)--->  [Detached SVGAnimatedLength] (native) @366422 [64 bytes]

The leak point shall be found by locating the objects that have strong reference on [Object] (object) @2516105 [688 bytes]

Would you mind providing advices about this feature? Or is there other ways to achieve this? Sincerely appreciates.

One alternative is to investigate other referrers (objects with references pointing to object @2480815 ) by using the following interactive CLI command:

memlab view-heap --snapshot <path-to-heap-snapshot> --node-id @2480815 

In the interactive view, press key 1, 2, 3, ... to focus on different windows and the one you may want to look into is the Referrers window (so press 1), hit enter to select a particular object (press up or down to select and hit enter to confirm) in that window, selected objects will be added to the Objects window (2), then press 2 to select objects in Object window to view the referrers' retainer trace (shown in the Retainer Trace window)

Screenshot 2024-01-12 at 10 49 09 AM

You might also be able to do something similar in Chrome DevTools.

I can tweak the algorithm to give less priority to this type of key in weak map key-value pair references when I get a chance.

@LZTWilliam I just double checked the code, I've excluded weakMap references to key (totally forgot it since it was added a while ago).

The edge you got is actually a WeakMap pointing to the value part of the key-value pair (i.e., it points to object @2480815 which is keyed by object @2516105). This is expected behavior since WeakMap is supposed to hold a strong reference to the value if there are strong references from somewhere else pointing to the key object.

--17779 / part of key (Object @2516105) -> value (system / JSProxy @2480815) pair in WeakMap (table @1827325) (internal)--->  [system / JSProxy] (hidden) @2480815 [704 bytes]
  ↑↑↑↑↑↑↑ THIS IS ACTUALLY A STRONG REFERENCE ↑↑↑↑↑↑↑

So you might want to use this command to figure out why the key object @2516105 is still alive. Here is some commands for investigation:

memlab trace --node-id @2516105 --snapshot <path-to-snapshot>
memlab view-heap --node-id @2516105 --snapshot <path-to-snapshot>

@JacksonGL Thanks for your reply! You correct a long-term misunderstanding for me.
And I found really weird and confusing thing on your reminder. Does these 2 lines means the value (JSProxy @2480815) has reference to the key (Object @2516105)?

  --17779 / part of key (Object @2516105) -> value (system / JSProxy @2480815) pair in WeakMap (table @1827325) (internal)--->  [system / JSProxy] (hidden) @2480815 [704 bytes]
  --1 (hidden)--->  [Object] (object) @2516105 [688 bytes]

It does make sense since the value (JSProxy @2480815) is a proxy created by key (Object @2516105). But will this lead to the result that the key will never be gc'd? Or there must be other codes in the framework to release the reference manually.

In addition, the key (Object @2516105) is only referenced by JSProxy @2480815. So I'm planning to find out what's happening in the framework and the lifecycle management of entries in that WeakMap.
image

Thanks again!

Does these 2 lines means the value (JSProxy @2480815) has reference to the key (Object @2516105)?

Yes

It does make sense since the value (JSProxy @2480815) is a proxy created by key (Object @2516105). But will this lead to the result that the key will never be gc'd? Or there must be other codes in the framework to release the reference manually.

@LZTWilliam I believe V8 should be smart enough to GC both key and value if 1) the only strong reference to the key comes from the value, and 2) there is no other strong references keeping the value alive.

Actually you can check with a small test case in Chrome's web console and V8 can indeed release both key and value if value points to key:

let map = new WeakMap();
let arr= [{}, {}]; 
arr[1].ref = arr[0]; 
map.set(arr[0], arr[1]);
// take a heap snapshot and see that 
// the WeakMap contains the key/value pair

// --------------
arr = [];
console.clear();
// take another heap snapshot and 
// now the WeakMap does not contain the key/value pair

It might be possible that in your case the key (object @2516105) is also a value in another key/value pair in the same WeakMap. Perhaps there is a long k/v pairs chain where the value of a pair is the key of another pair, but ultimately there should be a key kept alive by a strong reference outside of the WeakMap (or maybe V8 cannot handle such a long k/v pair chain case).

It might be possible that in your case the key (object @2516105) is also a value in another key/value pair in the same WeakMap.

I've checked the retain chain on several objects. I believe there's no such k/v pairs chain.

ultimately there should be a key kept alive by a strong reference outside of the WeakMap

@JacksonGL Definitely! This is why I'm looking for other powerful tools. Am I able to ignore all references from WeakMap(Under the assumption of no k/v pairs chain and key referenced by its value can be gc'd by V8) in memlab? It's way too complicated to find the "ultimately" manually in my heap snapshot. Most of the retain chain finally lead to WeakMap.

Is it possible to read the snapshot file, modify edges and nodes by IHeapSnaphost interface, and save it to another file?

Update: hacked this function and call findLeaks again, the results have been changed. Not sure on good or bad way. Still trying to find out...
image

I've checked the retain chain on several objects. I believe there's no such k/v pairs chain.

@LZTWilliam if that's the case you might want to check if there is other referrers / retainers keeping either the key or the value alive (please check out my earlier comment on how to do that).

Am I able to ignore all references from WeakMap(Under the assumption of no k/v pairs chain and key referenced by its value can be gc'd by V8) in memlab?

Yes, looks like you already had the code change (in your screenshot) to achieve this.

Is it possible to read the snapshot file, modify edges and nodes by IHeapSnaphost interface, and save it to another file?

MemLab currently does not support modifying or serializing heap snapshots.

Ignoring all WeakMap edge is very useful in my case. Found a self-written leak point and a third-party component leak point
Before fix:
image
After fix:
image
Memory leak has been reduced by 90%+

My best practice is:

1. Find the objects that have biggest shallow size, use `trace` to find a reference chain.
2. Analyze the chain, try to locate something familiar(whether in my own code or third-party component).
3. Eliminate the key reference, and check whether the leak remains.
4. If the leak remains, try step 1 again.

Considering the complexity of reference chain in javascript(especially with framework), an IEdgeFilter interface might be useful, providing an official way to ignore certain kind of references.

Anyway, thanks again for all the replies!