How do I properly dispose and free the memory used for V8.Net.V8Engine instances?

498 views Asked by At

I'm running into an issue when using my V8Engine instance, it appears to have a small memory leak, and disposing of it, as well as forcing the garbage collection doesn't seem to help much. It will eventually throw an AccessViolationException on V8Enging local_m_negine = new V8Engine() claiming a Fatal error in heap setup, Allocation failed - process out of memory and Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

Monitoring the program's memory usage through Task manager whilst running confirms that it is leaking memory, around 1000 KB every couple of seconds I think. I suspect it is the variables being declared within the executed script not being collected, or something to do with the GlobalObject.SetProperty method. Calling V8Engine.ForceV8GarbageCollection(), V8Engine.Dispose() and even GC.WaitForPendingFinalizers() & GC.Collect() doesn't prevent this memory being leaked (Although it is worth noting that it seems to leak it slower with these commands in place, and I know I shouldn't use GC but it was there as a last resort to see if it would fix the issue.)

A tangential issue that could also provide a solution is the inability to clear the execution context for V8Engine. I am required to dispose and re-instantiate the engine for each script, which I believe is where the memory leak is happening, otherwise I run into issues where variables have already been declared, causing V8Engine.Execute() to throw an exception saying such.

I can definitely confirm that the memory leak is something to do with the V8Engine Implementation, as running the older version of this program that uses Microsoft.JScript has no such memory leak, and the memory used remains consistent.

The affected code is as follows;

//Create the V8Engine and dispose when done
using (V8Engine local_m_engine = new V8Engine()) 
{
    //Set the Lookup instance as a global object so that the JS code in the V8.Net wrapper can access it
    local_m_engine.GlobalObject.SetProperty("Lookup", m_lookup, null, true, ScriptMemberSecurity.ReadOnly);
    //Execute the script
    result = local_m_engine.Execute(script);

    //Please just clear everything I can't cope.
    local_m_engine.ForceV8GarbageCollection();
    local_m_engine.GlobalObject.Dispose();
}

EDIT:

Not sure how useful this will be but I've been running some memory profiling tools on it and have learnt that after running an isolated version of the original code, My software ends up with a large amount of instances of IndexedObjectList's full of null values (see here: https://i.stack.imgur.com/07psx.jpg). It appears to have one instance of each class for each V8Engine instance that is made, but they aren't being disposed or freed. I cant help but feel like I'm missing a command or something here. The code I'm using to test and recreate the memory leak that the above implementation causes is as follows:

using System;
using V8.Net;

namespace V8DotNetMemoryTest
{
    class Program
    {
        static void Main(string[] args)
        {
            string script = @"  var math1 = 5;
                                var math2 = 10;
                                result = 5 + 10;";
            Handle result;
            int i = 0;
            V8Engine local_m_engine;
            while (true)
            {
                //Create the V8Engine and dispose when done
                local_m_engine = new V8Engine();

                //Set the Lookup instance as a global object so that the JS code in the V8.Net wrapper can access it
                //local_m_engine.GlobalObject.SetProperty("Lookup", m_lookup, null, true, ScriptMemberSecurity.ReadOnly);
                //Execute the script
                result = local_m_engine.Execute(script);
                Console.WriteLine(i++);

                result.ReleaseManagedObject();
                result.Dispose();

                local_m_engine.Dispose();
                GC.WaitForPendingFinalizers();
                GC.Collect();

                local_m_engine = null;

            }
        }
    }
}
1

There are 1 answers

0
James Wilkins On BEST ANSWER

Sorry, I had no idea this question existed. Make sure to use the v8.net tag.

Your problem is this line:

result = local_m_engine.Execute(script);

The result returned is never disposed. ;) You are responsible for returned handles. Those handles are struct values, not class objects.

You could also do using (result = local_m_engine.Execute(script)) { ... }

There is a new version released. I am finally resurrecting this project again as I will need it for the FlowScript VPL project - and it now supports .Net Standard as well for cross-platform support!