Object.create history and memory leaks

There are at least a few ways to create an object in JavaScript based on a given prototype object. One of them is the Object.create function that was a part of the ECMAScript 5 release. However, before ECMAScript 5 was introduced this function had had to be implemented by programmers themselves and it actually has existed in our programs in a few versions. That said, it’s worth digging into the history of Object.create to see how we used to make objects based on a prototype object. It can lead us to very interesting observations, particularly on how memory leaks can occur in our code and why optimization can be evil.

Version 1 (2006-06-07)

For many years Douglas Crockford’s proposal for prototypal inheritance has been the first choice solution for implementing the object inheritance based on a prototype chain. Although it has slightly evolved over years, it can be essentially expressed in a few lines of JavaScript:

function object(o) {
	function F() {}
	F.prototype = o;
	return new F();
}

In most cases it works great and is a sufficient tool for creating new objects when Object.create is not available (older browsers). However, things get worse when you are concerned about performance. If you pay close attention, you will easily notice that in order to make a new object, every single time a new function function F() {} needs to be initiated. It means a new piece of memory has to be assigned which takes extra space and time.

Even though it’s a rare issue for the most common tasks we deal with at work like home pages and small SPAs, and modern browsers have already adopted ECMAScript 5 Object.create so you possibly don’t even have to care about this, the optimized version becomes a quite interesting lesson of optimization and JavaScript memory leaks.

Version 2 (2007-10-21)

One of reasonable ways to speed up the above example is defining F function once and then reusing it. The oldest source of this improvement I could find was Richard Cornford’s post on comp.lang.javascript group:

var object = (function() {
	function F() {};
	return function(obj) {
		F.prototype = obj;
		return new F();
	};
}());

Richard wrote:

The assigning to the prototype does need to happen every time, but I don’t see why it is necessary to have a new constructor created for each execution of the function.

As you can see on the line 2 Richard defined function F() {} in the IIFE, which serves as a skeleton to create new objects based on whatever prototype we pass to object() as obj parameter. That works fine which means an empty function is created only once and it doesn’t matter how many objects will be produced.

However, this solution has a very important drawback, that can be overlooked and never get on the radar until we notice something weird is happening to the memory consumption in our application. It also shows, that code optimization can be dangerous. To figure out why, let’s make a new object:

var myObject = object({
	foo: "bar"
});

It works just fine, which can be tested by executing the following:

myObject.foo; // bar

The fun part starts however when myObject no longer references the same object. Let’s trigger it manually at the last line:

var myObject = object({
	foo: "bar"
});

myObject = null;

What happened is that we successfully reassigned myObject to no longer reference the object literal object, however, not everything is eligible for garbage collection. Notice that we passed an object literal to the object function:

{
	foo: "bar"
}

It occurs to be a problem, since this object actually gets stuck in the memory. It can be caught up if we analyzed the object body:

var object = (function() {
	function F() {};
	return function(obj) {
		F.prototype = obj;
		return new F();
	};
}());

In order to create object we defined F function in the IIFE. It stays in its scope forever and will be never collected out. Then, at the line 4 we assign a new prototype to it (our foo: bar object in this case). Eventually, the whole thing returns a new object formed by calling new F(). So after executing it for the first time we have a new object referenced by myObject, and it’s important that object function doesn’t keep track of it and it doesn’t know if it gets removed or whatever. What is most important is that our F function kept a reference to

{
	foo: "bar"
}

since F was defined in the IIFE and will never get removed from the memory until we close the browser window. It happens, because of assigning it to the F’s prototype:

F.prototype = obj;

So during the application life-time { foo: “bar” } is not eligible for garbage collection as it’s bundled with F.prototype. The only way to force this is creating a new object using object(). By doing this the old cached object will be eligible for garbage collection, however the new one will still leak. And it will happen over and over again every time we make a new object.

It shows by the way how “optimization is the root of all evil”. Version 2 is an optimization but version 1 will actually perform well enough in virtually all real world cases. So was the optimization ever worth it?

Version 3 (2013-04-11)

Leaving the answer for the previous question, the memory leak fix is relatively easy. I believe the originator of this was Andrea Giammarchi in his pull request for the Maria library. His change basically comes down to nulling the F’s prototype in object function (line 6):

var object = (function() {
	function F() {};
	return function(obj) {
		F.prototype = obj;
		obj = new F();
		F.prototype = null;
		return obj;
	};
}());

This way we manually break F’s reference to the prototype object so that F will not prevent that object from being garbage collected. The other interesting exercise might be to inspect modern engines and examine them to see how they handle such memory leaks as it was definitely an issue in the old good years of our beloved enemies like IE6. I made a tiny example of Version 2 at JSBin and then inspected Google Chrome’s 39.0.2171.95 GC following a nice video tutorial I found at YouTube:

Image 2015-01-14 at 2.01.24 PM

Summary

The above history timeline of Object.create alternatives is something I was really excited about at the time I discussed it with Peter Michaux. He used it in his great MVC library called Maria. Truth to be told, it perfectly illustrates the problem of memory leaks in JavaScript and what makes it even more great is that you can demonstrate it using just a few lines of code.

Update: Version 4 (2015-01-15)

After a short Twitter conversation with Andrea Giammarchi I was told about his another variation of Version 3 which seems to be more bullet-proof since nulling the prototype object might cause a browser crash in Opera/Debian:

var inherit = (function () {
	function Constructor() {
		// always reassign the initial
		// prototype to avoid problems
		// in jurassic engines
		Constructor.prototype = cp;
	}
	// original prototype
	var cp = Constructor.prototype;
	// the inherit function
	return function (object) {
		// assign it once
		Constructor.prototype = object;
		// and drop any reference once constructoed
		return new Constructor;
  	};
}());

Above inherit function is a part of es-class library by Andrea Giammarchi and avoid nulling the prototype object by restoring an original prototype object which is initially assigned to cp variable.


Thanks to Peter Michaux for his help with gathering some important facts about the topic.

Komentarze

1

Memory leak would be if on any object instantiation some memory is allocated that cannot be reclaimed…if only the last object prototype size cannot be gced big deal!

gillesev
2

Great article thanks. It was an enlightening read

Dodaj komentarz

Dozwolone tagi: <blockquote>, <code>, <strong>