Javascript object memory management when using delete on property - javascript

I'm currently writing a node.js/socket.io application but the question is general to javascript.
I have an associative array that store a color for each client connection. Consider the following:
var clientColors = new Array();
//This execute each new connection
socket.on('connection', function(client){
clientColors[client.sessionId] = "red";
//This execute each time a client disconnect
client.on('disconnect', function () {
delete clientColors[client.sessionId];
});
});
If I use the delete statement, I fear that it will make a memory leak as the property named after client.sessionId value(associative arrays are objects) won't be deleted, its reference to its value will be gonne but the property will still exist in the object.
Am I right?

delete clientColors[client.sessionId];
This will remove the reference to the object on object clientColors. The v8 garbage collector will pick up any objects with zero references for garbage collection.
Now if you asked whether this created a memory leak in IE4 then that's a different question. v8 on the other hand can handle this easily.
Seeing as you deleted the only reference the property will also be gone. Also note that objects are not "associative arrays" in javascript since ordering is implementation specific and not garantueed by the ES specification.

Since clientColors[client.sessionId] is a primitive value (a string) in this case, it will be cleared immediately.
Let's consider the more complicated case of foo[bar] = o with o being a non-primitive (some object). It's important to understand that o is not stored "inside" foo, but somewhere in an arbitrary memory location. foo merely holds a pointer to that location. When you call delete foo[bar], that pointer is cleared, and it's up to the garbage collector to free the memory taken by o.
BTW: You shouldn't use Array when you want an associative array. The latter is called Object in Javascript and is usually instantiated using the short-hand quasi-literal {}

If you are using the V8 engine or nodejs/io, it may not lead to a leak but it is always advisable to prevent leaks.
Just delete it
delete clientColors[client.sessionId];
Or set it to null
clientColors[client.sessionId] = null;
Which will also cascade to any prototypically inherited objects.
This way there is almost no probability of starting a leak.

Related

Is it possible to get notified just before an object is about to get garbage collected?

The Question
With FinalizationRegistry, it's possible to get notified after that an object has been garbage collected. However, is it possible to get notified before, so I can still have access to the data and do something with it?
What I'm trying to achieve
I want to implement a CompressedMap<K, V> were data is internally stored, either deflated in a Map<K, Buffer> or inflated in a Map<K, WeakRef<V>>. It's up to the user to define the deflate and inflate functions.
As a classic Map<K, V>, if the user holds a reference to a value present in the map, and update it, it should also be automatically updated in the map (because it's the same object). That's why I need to keep the values in a Map<K, WeakRef<V>> and compress and move them to the Map<K, Buffer> only when they're about to get garbage collected.
What I've already considered
SO question: Can I get a callback when my object is about to get collected by GC in Node?
The accepted answer shows how to use FinalizationRegistry which fires a callback AFTER that the object has been garbage collected and is no longer available.
Moving the value to the deflated map after each modification
It would require to wrap each fields of the object in a getter/setter and it has lot of implications:
It's more computational intensive to update the deflated map after EACH modification.
Modifications on new fields (not wrapped in getter/setter) would be ignored.
Wrapping each fields of each objects could have a big memory impact on large map which would defeat the purpose of a "compressed map".
It would modify the user's objects.
It questions where the boundary of the object is. Maybe we should wrap all the fields even deep ones, maybe not. It depends of the user's use case.
Writing a Node.JS addon and using Node-API
I didn't dig deeply into it, but it would be a last resort solution, because my implementation will only be compatible with Node.JS. Even if I'm focused on Node.JS, browser support would be nice to have. Also I never wrote a Node.JS addon, and I'm not even sure if it will allow me to implement a PreFinalizationRegistry.
References
FinalizationRegistry
Map
WeakRef
Developers shouldn't rely on cleanup callbacks for essential program logic. Cleanup callbacks may be useful for reducing memory usage across the course of a program, but are unlikely to be useful otherwise.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry#notes_on_cleanup_callbacks
Instead of waiting until the object is finalized, I would recommend using setTimeout() to deflate it when it hasn't been used for a certain period of time.
To do so you'll want to return an object that behaves like a Map<K, WeakRef<V>> and wraps it instead of the actual map. This way you can start throwing exceptions if it is used after the timeout.
Imagine an unreferenced object {a: 1, b: {c: 2}} for which there is still a reference to its b subobject. If the object was compressed and then garbage-collected, the holder of the reference to b might say ref_to_b.c = 3, and this change would not automatically be reflected in the compressed version. So when the compressed version is later re-inflated, it still has b.c = 2.
This means that you can only compress those members of an object that are not themselves objects, that is, the primitive-valued members. And this could be done with a setter whenever such a value is changed. The deflated values would be kept with strong references, so that an object can always be recreated from them if only its key is known, even if its earlier incarnation has been garbage-collected.
class DeflatableObject {
static deflated = {
primitive: new Map(),
subobject: new Map()
}
static recreate(key) {
var obj = new DeflatableObject();
obj.key = key;
obj._primitive = inflate(DeflatableObject.deflated.primitive.get(key));
var subobj = DeflatableObject.deflated.subobject.get(key);
if (subobj)
obj._subobject = subobj.object.deref() || DeflatableSubobject.recreate(subobj.key);
return obj;
}
set primitive(value) {
this._primitive = value;
DeflatableObject.deflated.primitive.set(this.key, deflate(value));
}
get primitive() {
return this._primitive;
}
set subobject(value) {
this._subobject = value;
DeflatableObject.deflated.subobject.set(this.key, {
object: new WeakRef(value),
key: value.key
});
}
get subobject() {
return this._subobject;
}
}

Can a bi-directional WeakMap lead to a memory leak?

From what I know about native JavaScript WeakMaps, they store their keys weakly, but store their values strongly, is that correct?
If so, would using a value as both, a key and a value in a WeakMap prevent the object from being garbage-collected?
const domElement0 = ...;
const domElement1 = ...;
const map = new WeakMap([
[ domElement0, domElement1 ],
[ domElement1, domElement0 ]
]);
Assuming that the WeakMap is always available, in that snippet, I would assume that, if domElement0 is unreachable, then domElement1 would not be garbage-collected, and vice versa, but if they both become unreachable and detached from the DOM, can I know that they will both become eligible for GC in most browsers?
If so, can someone provide a reference to why this is possible?
I think they should become garbage.
The specification of WeakMap Objects has the following note:
WeakMap and WeakSets are intended to provide mechanisms for dynamically associating state with an object in a manner that does not “leak” memory resources if, in the absence of the WeakMap or WeakSet, the object otherwise became inaccessible and subject to resource reclamation by the implementation's garbage collection mechanisms.
So if the only references to the objects are through a WeakMap entry, and the keys are not accessible through some chain of references starting outside the WeakMap, the objects become garbage.

Is it necessary to nullify primitive values for grabage collection?

If I have the following code:
function MyClass() {
this.data = {
// lots of data
};
}
var myClassInstace = new MyClass();
var myobj = {
num:123,
str:"hello",
theClass:myClassInstance
};
I know it's absolutely necessary to do:
myobj.theClass = null;
To free up myClassInstance and its data property for GC. However, what should I do with myobj.num and myobj.str? Do I have to give them a value of null too? Does the fact that they're primitive change anything regarding GC?
The JavaScript runtime that implements garbage collection will be able to collect items as soon as values are no longer reachable from code. This is true for object references as well as primitives. The details of the exact moment the item is collected varies by implementation, but it is not even necessary to set your object references to null (as you state) unless you need the object cleaned up sooner than the natural termination of the current function.
This all ties into the fundamental concept of "scope" and the Scope Chain. When an item is no longer in any other objects scope chain it can be collected. Understanding this clearly will answer this question and also help to understand closures, which are scenarios where items stay in memory longer than you might have expected.
There are a lot of "it depends here", ranging from what your code is doing to what browser you're running in. However, if your object is JIT compiled to not use a map for its attributes, then the number should be an 8 byte double stored inline inside the object. Nulling it will do nothing.
The string and the myclass instance will be a pointer to memory allocated outside the object (since a string can be arbitarily many bytes, it can't be stored inside the object. A compiler could conceivably store one instance of the string in memory and never free it, however). Nulling them can allow the garbage collector to free them before the main object goes out of scope.
However, the real question is why you're worried about this. Unless you have profiled your code and identified garbage collection or memory leaks as a problem, you should not be trying to optimize GC behavior. In particular, unless your myobj object is itself going to be live for a long time, you should not worry about nulling fields. The GC will collect it when it goes out of scope.
setting to undefined (not null) will work however delete is better example delete myobj.theClass
Just to avoid misunderstanding I will say that there is no way to really delete an object from memory in JavaScript. you delete it's references or set them to undefined so that the GC can do it's work and really delete.

How to delete objects created with `new` in JavaScript?

I'm new to JavaScript and come from C++ background. This will sound silly but I can't find how to delete objects created with new in JavaScript.
Here's an example:
function Article (id) {
this.content = db.get('article', "id:" + id);
...
}
var article = new Article(5);
Every instance of Article allocates memory as it gets data from the database (in my case content of the article). This leads my application to quickly grow to gigabytes in size of memory usage.
How do I release memory in JavaScript? I found delete but it appears to delete array and hash elements rather than Objects.
Just remove all references to the object, and it will be garbage collected (when the JS engine does some garbage collection).
article = undefined; // or some other value
It happens automatically through garbage collection at some point in the future, or never. It is non-deterministic, unlike RAII in C++.

IE8 compliant weakmap for DOM node references

I want to have literally a Dictionary<Node, Object>
This is basically an ES6 WeakMap but I need to work with IE8.
The main feature I want is
minimize memory leaks
O(1) lookup on Object given Node.
My implementation:
var uuid = 0,
domShimString = "__domShim__";
var dataManager = {
_stores: {},
getStore: function _getStore(el) {
var id = el[domShimString];
if (id === undefined) {
return this._createStore(el);
}
return this._stores[domShimString + id];
},
_createStore: function _createStore(el) {
var store = {};
this._stores[domShimString + uuid] = store;
el[domShimString] = uuid;
uuid++;
return store;
}
};
My implementation is O(1) but has memory leaks.
What's the correct way to implement this to minimize memory leaks?
In an article I wrote recently, ES 6 - A quick look at weak maps, I explained how jQuery is able to make data() leak free. It basically generates an expando property name, jQuery.expando. When you attach data to an element, the data is pushed to an internal cache array, and the element is given the expando property with a value of the index of the data in the cache. Something similar to this:
element[jQuery.expando] = elementId;
The way to prevent circular references is to not attach objects directly to elements as expandos. If a reference to the element remains in code, then that element cannot be garbage collected even if it is removed from the DOM. However, preventing circular references doesn't plug the leak entirely - there's still data left in the array if the element is removed from the DOM and garbage collected. So jQuery clears the array on page unload, as well as removing data from the array if elements are removed from the DOM using its own methods like remove(). It keeps the data alive for detach().
The reason jQuery does this, is because there is no weak map equivalent, it's kind of shimmable in ES 5 but not in ES 3. As explained in my article, WeakMap is made for exactly this kind of situation, but the only available implementation is in Firefox 6 and above and, with the spec not being finalized, even that shouldn't be used in production environments.
Another thing to take from my article is that certain elements will not allow you to attach expando properties — <object> and <embed> are the two culprits named and shamed in the jQuery source code. For these element's, you're pretty much screwed and jQuery just will not let you use data on them.
Basic circular reference memory leaks occur in reference counted implementations when two object's properties hold direct references to each other. So DOMObject holds a reference to JSObject and vice versa. Assuming there are no other references to either object, they'd both have a permanent reference count of 1 and the GC would not mark them for collection.
Older browsers (IE6) wouldn't break these circular references, even on page unload, whilst newer browsers are able to break many of these circular references by recognizing the patterns that cause them. jQuery.cache and similar patterns partially void memory leaks because DOMObject never holds a reference to JSObject so, even when JSObject holds a reference to DOMObject, the GC can still mark JSObject for collection when there are no more references to it. Once the GC has collected JSObject, the reference count for DOMObject will be reduced, freeing that up for collection also.
Although IE 8+ and other reference counting browsers may be able to break many circular reference patterns (around 400 were fixed for IE 8), the likelihood of leaks is only reduced. For instance, I've seen a huge leak in one of my own apps in IE 8, when working with script elements and JSONP. The best solution is to plan for the worst and, without WeakMap(), the best you can do is use the jQuery data pattern. Sure, you might be risking having orphaned objects, but this is the lesser of two evils.

Categories