Finding memory leaks in Chrome 66 has become much more convenient. DevTools can now trace, snapshot DOM objects from C ++, display all available DOM objects from JavaScript, along with links to them. The appearance of these features was the result of a new C ++ tracing mechanism in the V8 garbage collector.
Let me remind you that stable Chrome now (03/20/2018) has version 65, so to marvel at the feature, you will have to install one of the unstable assemblies (for example, Beta has version 66, and Dev and Canary - 67).
Memory leaks in garbage collection occur when an unnecessary object is no longer collected due to unintentionally added references from other objects. Memory leaks in web pages often occur when JS objects and DOM elements interact.
Let's look at a toy example that shows a leak that occurs when a programmer forgets to remove an event handler. The handler references objects, and they can no longer be deleted. In particular, the iframe window flows.
// Main window: const iframe = document.createElement('iframe'); iframe.src = 'iframe.html'; document.body.appendChild(iframe); iframe.addEventListener('load', function() { const local_variable = iframe.contentWindow; function leakingListener() { // Do something with `local_variable`. if (local_variable) {} } document.body.addEventListener('my-debug-event', leakingListener); document.body.removeChild(iframe); // BUG: forgot to unregister `leakingListener`. });
Worse, the leaked iframe keeps all its JS objects alive.
// iframe.html: class Leak {}; window.global_variable = new Leak();
To find the cause of a leak, it is important to understand the concept of a suspended path (retaining path). A hanging path is a chain of objects that interfere with the assembly of a leaking object. The chain starts from some root object, like a global object of the main window. The chain ends with a leaking object. All intermediate objects have a direct link to the next object in the chain. For example, the suspended path of the Leak
object and this iframe looks like this:
Notice that the path hangs through the border between JavaScript and DOM (they are marked in green and red, respectively) two times. JS objects live on the V8 heap, and DOM objects are C ++ objects in Chrome.
Now we can study the suspended path of any selected object using the heapshot snapshot. At the same time, all objects in the V8 heap will be saved exactly. Most recently, only very approximate data about C ++ DOM objects was stored there. For example, Chrome 65 shows an incomplete hanging path for a Leak
object from the previous toy example:
Only the first line is fairly accurate: the Leak
object is actually stored in global_variable
in the frame window. All other lines are trying to approximate the real path, and this makes debugging memory leaks very difficult.
Starting in Chrome 66, DevTools traces C ++ DOM objects, and precisely captures objects and links between them. This feature is based on a powerful new mechanism for tracing C ++ objects, which was created for cross-component garbage collection. As a result, the paths in DevTools became correct!
File for experiments: https://ulan.imtqy.com/misc/leak.html
DOM objects are controlled by Blink, the rendering engine used in Chrome, which is responsible for translating DOM into real text and images on the screen. Blink and its internal implementation of DOM are written in C ++ - and this means that the DOM cannot be directly reflected in JavaScript. Instead, the objects in the DOM are divided into two parts: the wrapper object accessible from JS, and C ++ the object representing the node representation from the DOM. These objects contain direct links to each other. Determining the lifetime and area of ownership of components that cross the boundaries of several systems, in this case Blink and V8, is a rather difficult task because it involves parties who need to agree in advance which components are still alive and which ones should be disposed of.
In Chrome 56 and older versions (for example, until March 2017), Chrome used a mechanism called object grouping. Objects are linked into one group if they belong to the same document. The group, and all its objects, are kept alive as long as there is at least one living object at the end of another suspended path. This makes sense in the context of DOM nodes, which are always associated with the documents containing them, forming the so-called DOM trees. But this abstraction loses all the real hanging paths, which used to make debugging very difficult. As soon as the objects ceased to fit the above scenario (for example, closures in JavaScript used as event handlers), it became difficult to implement this approach and led to bugs in which JS wrappers were assembled ahead of time, which in turn led to their replacement with empty ones. JS-wrappers with the complete loss of all properties.
Beginning with Chrome 57, this approach has been replaced by "cross-component tracing" - a mechanism that determines the vividness of objects, tracing them from JavaScript to the C + + implementation in the DOM, and the same way back. On the C ++ side, incremental tracing is implemented, which creates write barriers in order not to slip into the stop-the-world. Cross-component testing not only improves latency, but also better approximates the vibrancy of objects at the component boundary, and repairs several frequently occurring scenarios that used to lead to leaks. In addition, because of this, DevTools were able to do snapshots that truly reflect the state of the DOM.
Minute advertising. As you probably know, we do conferences. The nearest JavaScript conference is HolyJS 2018 Piter , which will be held May 19-20, 2018 in St. Petersburg. You can come there, listen to the reports (what reports are there - described in the conference program ), talk live with practicing experts from JavaScript and the frontend, developers of various modern technologies. In short, come in, we are waiting for you!
Source: https://habr.com/ru/post/351620/