Swimming in false retainer pathsSuppose you were working on the Microsoft Edge new-tab page (edge://newtab) and wanted to know why the function named
observe()is being kept alive. You would hit F12 to open DevTools, navigate to the Memory tab, and select the relevant object in the top pane. In Microsoft Edge version 94, you would see four retainers for that function: The shortest path goes through a
FeedbackVector, an internal data type which collects data about what functions have been called by another function. Expanding that path a little more, you would see that the
FeedbackVectoris owned by another function,
HTMLElement.attachShadow(): So, there’s this function which retains some internal V8 goo that you have no control over which, in turn, retains the original function
observe(). How can you possibly break that link, since it’s all V8 internals? You can’t, which means there’s a bug. Either V8 is leaking memory, or the Retainers pane is incorrect, and both are serious high-priority issues for us. In this case, the problem was that Microsoft Edge showed incorrect retainers. We have fixed this issue in Microsoft Edge 99. Here is the correct answer list of retainers: Because the Retainers tree is both deep and wide on large apps, the number of false paths completely blocked many memory usage investigations. This problem was so pervasive that our friends at Microsoft Teams have been using a workaround which captures additional information during V8’s garbage collection phase and highlights a single retainer path which is actually known to be true.
Keeping it fixedIt’s very important that developers can trust DevTools, so we are working on an automated solution to prevent regressions that introduce new false retainer paths. Based on the insight from Microsoft Teams that the heap snapshot needs to match the garbage collector’s marking behavior, we have proposed validation code that compares the outgoing edges for each node in the heap snapshot versus the objects visited by the actual garbage collection code and crashes the process if there is any mismatch. This validation will be included in debug builds (including those that we use for daily automated testing), so we can quickly find and fix any problems that cause a mismatch between the snapshot and the garbage collector’s behavior.
Finalization registriesIn yet another excellent report from Microsoft Teams, we saw that a bunch of FinalizationRegistry objects were retained directly by other FinalizationRegistries, and causing high memory usage: This turned out to be another V8-internal memory leak, which we’ve fixed as well.