Written some X++ which seems to have high memory usage when run in IL but runs normally when not in IL? Or just noticing high memory usage in servers running IL code?
Here we're going to talk about an aspect of garbage collection in XppIL that is important for anyone writing XppIL code to take on board.
Let's start with an example: If the code below runs in XppIL, when we hit the sleep line how many X++ objects are in memory (the query Cust contains CustTable and CustTrans):
Did you think it would be 1 query, 1 queryRun? The answer might be surprising, there are:
This demonstrates that instances of X++ kernel objects are not disposed until their XppIL owner object is disposed itself. If I ran the code above in an X++ job, it would have 6000 objects in memory just before the end of the job, when the job completes then they are all released. This is the key fact for an X++ developer to take on board - writing a process which will run for a long time and uses a lot of X++ kernel objects, it is this behaviour that could mean your code uses a lot more memory than you might expect.
What happens under the hood when we call something like Classes\Query in XppIL?
First it’s important to understand that the class Query although we recognise it as a kernel X++ class, there is no X++ code involved – it’s a native C++ object. This is the case for all kernel X++ classes – the classes associated with the query framework, TreeNode, Connection and so on.
This means when we’re calling a kernel X++ class in our XppIL code that we’re transitioning from managed (.NET) code to native code. The .NET garbage collector has no power over objects on the native side - they are out of its jurisdiction. The transition between these two layers is our interop layer, this is a part of our kernel and has to make a choice about how it’s going to try to track and release the native objects being used.
To understand how we’re going to track and release these objects we have to think about possible garbage collection approaches. For a bit of background on X++ and IL garbage collection this article by Peter Villadsen is excellent:
Basically Peter's article explains that the old X++ approach was to track objects, by incrementing and decrementing references to them as they instantiated or assigned, and releasing them at the soonest possible moment when they are no longer needed. This didn’t scale very well because as you add more and more objects in memory at once, there’s a bigger and bigger overhead to all the tracking the references of objects – each time you need to add or remove a reference there’s a big tree of references to look at.
The .NET (and therefore XppIL) approach is different, it runs periodically clearing objects it deems to be no longer needed. This means you don’t know exactly when your object will leave memory, but it also means that it scales very well, as it’s always doing the same sweep when the GC runs to collect objects.
The reason that I referenced Peter’s article on garbage collection, and briefly explained the two approaches that old X++ and XppIL take in this respect, is because it provides the background necessary to appreciate why we take a certain approach with our interop when it comes to native objects used in IL.
What possible options might there be for trying to track the native kernel X++ objects used in XppIL so they can be garbage collected?
I am explaining this here, to help others understand why we took this approach with AX2012 - we had to make a technical choice and the option we choose gives the best performance.
We took the third option above for AX2012. What this means is that native objects can stay in scope longer than you might have expected when compared with old X++. So when writing X++ code that will run in IL, it’s important to realise this, and consider the impact you might have on memory when your code runs in IL.
There are a few tricks to help the X++ developer with this, this is a non-exhaustive list, but it’s intended as a good base to start with:
Nice article thanks...
Hello, Great article thanks a lot!
However, I don't get the part that said "If I ran the code above in an X++ job, it would have 6000 objects in memory just before the end of the job, when the job completes then they are all released."
If I'm no miscorrect, an X++ job runs in interpreted mode and I thought that the X++ garbage collector being deterministic, it would have released the query and queryRun objects from memory each time the assignment that change the reference is made.
Am I wrong?