Javascript garbage collection mechanism with memory leak detail resolution

  • 2020-03-27 00:08:06
  • OfStack

Javascript has an automatic garbage collection mechanism, which means that the execution environment is responsible for managing the memory used during code execution. In languages like C and C++, one of the basic tasks of developers is to manually track memory usage, which is a source of many problems. When writing javascript programs, developers no longer have to worry about memory usage, and the allocation of required memory and garbage collection are fully automated. The principle of this garbage collection mechanism is simple: find out which variables are no longer in use, and then free up the memory used. To do this, the garbage collector periodically performs this operation at a fixed interval (or a preset collection time in code execution).

Now let's analyze the normal life cycle of a local variable in a function. Local variables exist only during the execution of a function. In this process, local variables are allocated space in stack (or heap) memory to store their values. These variables are then used in the function until the end of the function execution. At this point, there is no need for local variables to exist, so their memory can be freed for future use. In this case, it is easy to determine whether variables are still necessary; But not all cases are so easy. The garbage collector must keep track of which variables are useful and which variables are not, and mark variables that are no longer useful in order to reclaim their memory in the future. The policies used to identify garbage variables may vary from reality, but specific to the implementation in the browser, there are usually two policies.

Mark clear

The most common form of garbage collection in javascript is mark-and-sweep. When a variable enters the environment (for example, when a variable is declared in a function), the variable is marked "enter the environment." Logically, you can never free up memory for variables that go into the environment, because they can be used as long as the execution stream goes into the appropriate environment. When a variable leaves the environment, this marks it as leaving the environment.

Variables can be marked in any way. For example, you can record when a variable entered the environment by flipping a particular bit, or you can use a list of variables that entered the environment and a list of variables that left the environment to track which variables changed. At the end of the day, it doesn't really matter how variables are marked, it's the strategy.

At run time, the garbage collector marks all variables stored in memory (of course, you can use any notation). It then removes the variables in the environment and the variables referenced by the variables in the environment. Variables that are marked after that are considered to be ready for deletion because they are no longer accessible to variables in the environment. Finally, the garbage collector completes the memory sweep, destroying the marked values and reclaiming the memory space they occupy.

As of 2008, the javascript implementations of Internet explorer, Firefox, Opera, Chrome, and Safari all used a tag-clearing garbage collection strategy (or something similar) with different garbage collection intervals.

Reference counting

Another less common garbage collection strategy is called reference counting. Reference counting means tracking the number of times each value is referenced. When a variable is declared and the value of the reference type is assigned to that variable, the number of references to that value is 1. If the same value is assigned to another variable, the number of references to that value is increased by 1. Conversely, if the variable that contains a reference to the value gets another value, the number of references to the value is reduced by 1. When the number of references to the value becomes 0, there is no way to access the value, so the memory space it occupies can be reclaimed. This way, the next time the garbage collector runs, it will free up memory for values with zero references.

Netscape Navigator 3.0 was the first browser to use the reference counting strategy, but it soon ran into a serious problem: circular references. Circular reference refers to the fact that object A contains A reference to object B, and object B contains A reference to object A.

Here's an example:


function () {
    var objectA = new Object();
    var objectB = new Object();

    objectA.someOtherObject = objectB;
    objectB.anotherObject = objectA;
}

In this example, objectA and objectB refer to each other through their properties, that is, both objects are referred to 2 times. In an implementation with a lead tag cleanup strategy, both objects are out of scope because the function is executed. So these two cross-references are not a problem. But in an implementation with a reference counting strategy, objectA and objectB will continue to exist after the function is executed, so their number of references will never be zero. If this function is called repeatedly, a large amount of memory will not be recovered. As a result, Netscape abandoned the reference counter approach in Navigator 4.0 and instead implemented its garbage collection mechanism with mark-sweep. But the trouble with reference counting doesn't end there.

We know that some of the objects in IE are not native javascript objects. For example, the objects in BOM and DOM are implemented as COM (Component Object Model) objects using C++, while the garbage collection mechanism of COM objects adopts the reference counting strategy. Therefore, even if the javascript engine of IE is implemented using a mark-clearing policy, the COM objects accessed by javascript are still based on the reference-counting policy. In other words, as long as COM objects are designed in IE, there is always the problem of circular references.

The following simple example shows the circular reference problem caused by using COM objects:


var element = document.getElementById("some_element");
var myObject = new Object();
myObject.element = element;
element.somObject = myObject;

The example creates a circular reference between a DOM element (element) and a native javascript object (myObject). Where, the variable myObject has an attribute named element pointing to the element object; The variable element also has an attribute called someObject that refers back to myObject. Because of this circular reference, even if the DOM in the example is removed from the page, it will never be recycled.

To avoid circular references like this, it's best to manually disconnect native javascript objects from DOM elements when you're not using them. For example, the circular reference created in the previous example can be eliminated with the following code:


myObject.element = null;
element.somObject = null;

Setting a variable to null means cutting off the connection between the variable and the value it previously referenced. But the next time the garbage collector runs, it will delete these values and reclaim the memory they occupy.

Performance issues

Garbage collectors run periodically, and if the amount of memory allocated for variables is objective, then the collection effort is considerable. In this case, determining the garbage collection interval is a very important issue. When it comes to how often the garbage collector runs, one can't help but think of IE's notorious performance problems. IE's garbage collector runs on memory allocations, specifically 256 variables, 4096 object (or array) literals and array elements (slots) or 64KB strings. At any of these thresholds, the garbage collector runs. The problem with this implementation is that if a script contains so many variables, it is likely that the script will keep so many variables in its life. As a result, the garbage collector may have to run frequently. As a result, IE7 overwrote its garbage collection routine at the start of the resulting severe performance problems.

With the release of IE7, the garbage collection routines of its javascript engine have changed the way they work: thresholds for variable assignments, literals, and/or array elements that trigger garbage collection have been adjusted to dynamically correct. The thresholds in IE7 are equal to IE6 when initialized. If the routine recycles less than 15% of the memory allocation, the threshold values for variables, literals, and/or array elements are doubled. If the routine recovers 85% of the memory allocation, the various critical resets are set to default values. This seemingly simple tweak greatly improves the performance of IE when running pages that contain a lot of javascript.

In fact, some browsers can trigger a garbage collection process when we don't recommend it. In IE, a call to the window.collectgarbage () method immediately points to garbage collection, and in Opera7 and later, a call to widnow.opera. Collect () also starts the garbage collection routine.

Memory management

By programming a language with a garbage collection mechanism, developers generally do not have to worry about memory management. However, javascript's problems with memory management and garbage collection are a bit different. One of the most important issues is that the amount of usable memory allocated to web browsers is usually less than that allocated to desktop applications. This is done for security reasons, to prevent the javascript page from running out of system memory and causing the system to crash. The memory limitation problem affects not only the allocation of memory to variables, but also the call stack and the number of statements that can be executed simultaneously in a thread.

Therefore, ensuring minimal memory usage gives the page better performance, and it is best to free its reference by setting its value to null -- a practice called dereferencing. This practice is used for properties of most global variables and global objects. Local variables are automatically de-referenced when they execute the environment, as shown in the following example:


function createPerson (name) {
    var localPerson = new Object();
    localPerson.name = name;
    return localPerson;
};
var gllbalPerson = createPerson("Nicholas");
//Manually remove the globalPerson reference
globalPerson = null;

In this example, the variable globalPerson gets the value returned by the createPerson() function. Inside the createPerson() function, we create an object and assign it to the local variable localPerson, then we add a property named name to the object. Finally, when this function is called, localPerson is returned as a function and assigned to the global variable globalPerson. Since localPerson left its execution environment after the createPerson() function executed, there is no need to de-reference it as shown. But for the global variable globalPerson, we need to manually de-reference it when we're not using it, which is what the last line of code in the above example is for.

However, unreferencing a value does not automatically reclaim the memory it occupies. The real effect of de-referencing is to take the value out of the execution environment and recycle it the next time the garbage collector runs.

A memory leak

Closures cause special problems in IE because IE USES different garbage collection routines for JScript objects and COM objects. Specifically, if an HTML element is held in the scope chain of a closure, it means that the element cannot be destroyed. Here's an example:


function assignHandler () {
    var element = document.getElementById("someElement");
    element.onclick = function () {
            alert(element.id);
    };
};

The above code creates a closure that ACTS as an element time handler, which in turn creates a circular reference. Because the anonymous function holds a reference to the active object of assignHandler(), it is impossible to reduce the number of element references. As long as the anonymous function exists, the element has at least one reference, so the memory it consumes will never be recycled. However, this problem can be solved by rewriting the code a bit, as follows:

function assignHandler () {
    var element = document.getElementById("someElement");
    var id = element.id;

    element.onclick = function () {
            alert(id);
    };

    element = null;
};

In the above code, you eliminate circular references by storing a copy of element.id in a variable and referencing that variable in a closure. But that alone won't solve the memory leak problem. It is important to remember that the closure refers to the entire active object that contains the activity of the function, which contains the element. Even if the closure does not directly reference element, a reference is still saved in the active object that contains the function. Therefore, it is necessary to set the element variable to null. This allows you to de-reference the DOM object, smoothly reducing the number of references, and ensuring that the memory it consumes is properly recycled.

instructions

1. If you keep the object reference in another window, memory will not be freed even if the window is closed.

2. What's worse, if you keep a reference to a DOM object and close the window where the object is located, IE will crash and report a memory error (or require a reboot).


Related articles: