This project has moved. For the latest updates, please go here.

I get memory leaks without running CollectGarbage

Dec 30, 2014 at 1:17 AM
Edited Dec 30, 2014 at 1:18 AM
Hi I have a script that is simply:
function foo(items) {
    items.toSomething(); // toSomething is a .NET function that does something to the list and returns a new one.
}

loop { // this loop runs many times
     var items = new List<int>(); // This list grows over time.
    _engine.Script.foo(items);
}
I find that if I do not collect garbage eventually I end up with a memory leak, what is it holding onto I assume the collection passed into the method? running garbage collection impacts my performance, is there something else I can do?



Thanks
Coordinator
Dec 30, 2014 at 1:52 PM
Hi Stefan,

I find that if I do not collect garbage eventually I end up with a memory leak, what is it holding onto I assume the collection passed into the method?

The CLR and the script engine have independent object systems and garbage collectors. When you pass a .NET object to script code (or vice versa), a proxy is created on the receiving side that provides access to the actual object on the sending side.

By necessity, the proxy represents a reachable reference to the actual object and prevents it from being collected. Only when the proxy is collected does the actual object become available to its garbage collector, and only if no other references exist on either side.

The issue you're running into, most likely, is that V8's garbage collector is very lazy and relies on external triggers. Specifically, V8 needs the host to say, "Hey V8, now would be a good time to collect some garbage" (CollectGarbage(false) in ClearScript), or "Hey V8, I'm running low on memory, collect all the garbage you can no matter how long it takes" (CollectGarbage(true) in ClearScript).

running garbage collection impacts my performance, is there something else I can do?

Sometimes V8 can detect garbage collection opportunities (or emergencies) internally, but it seems to favor performance very heavily over memory efficiency without external prodding. Therefore it's up to the host to track its own condition (e.g., detect idleness, low-memory situations, etc.) and send the appropriate notifications via CollectGarbage().

Good luck!
Dec 30, 2014 at 11:05 PM
Is there a way to ensure collection of this reference without calling CollectGarbage? can I dispose that individual reference perhaps?
Coordinator
Dec 31, 2014 at 3:24 AM
Sorry, are you asking about ensuring collection of a managed object that you've passed into the script engine?
Dec 31, 2014 at 3:26 AM
Correct,

I am basically wanting to perform my original code sample without leaking code and without having to take the hit of calling CollectGarbage(true) on every call like so:

loop { // this loop runs many times
 var items = new List<int>(); // This list grows over time.
_engine.Script.foo(items);
_engine.CollectGarbage(true);
}


That works but it does not perform the best.



Thanks
Coordinator
Dec 31, 2014 at 7:24 PM
Edited Dec 31, 2014 at 7:24 PM
Hi Stefan,

A few thoughts:

  • Calling CollectGarbage() every time is almost certainly overkill. Yes, it causes the script engine to release managed objects it's no longer holding, but those objects will stick around until the managed garbage collector kicks in, and that's very unlikely to happen at the end of each loop iteration unless you invoke it explicitly, especially if we're talking about a tight loop. Instead, consider calling CollectGarbage() periodically, or based on a schedule that makes sense for your application - possibly something as simple as calling it once for every n loop iterations.
  • Be aware also that unmanaged proxies will pile up within the script engine unless you call CollectGarbage() at some point; that's unavoidable. V8 will clean them up eventually, but we've found that when it comes to garbage collection, V8 is lazy enough to get itself into trouble - a sort of "garbage thrashing" state that kills performance.
  • If you really need the script engine to release an individual managed object that you've passed into it - something that makes sense for very large objects, for example - then consider exposing it indirectly. In this case, instead of passing a large list, pass a tiny forwarding wrapper that implements the same IList<int> interface. When you're done, reset the wrapper's internal reference and the large list is ready to be collected. The script engine never touches it at all.
Cheers!