New resources available on Microsoft Learn
Did you know that Microsoft Learn offers free training modules to assist you on your path to mastering Dynamics 365 for Finance and Operations? Become an expert at your own pace or share with your team to foster growth.
Dynamics 365 2019 release wave 2 plan Discover the latest updates to Dynamics 365.Release Plan | Weekly Deployment Notes
Ace your Dynamics 365 deployment with packaged services delivered by expert consultants.Explore service offerings
Connect with the ISV success team on the latest roadmap, developer tool for AppSource certification, and ISV community engagements.
ISV self-service portal
The FastTrack program is designed to help you accelerate your Dynamics 365 deployment with confidence.
FastTrack Program | Finance and Operations TechTalks | Customer Engagement TechTalks | Talent TechTalks
Dynamics AX is a 3-tier application that evolved from a 2-tier application. Yes, this is right, the first versions of Axapta was solely a client side application communicating with the database. In version 2.0 the middle tier was introduced. The X++ language got a few new keywords, client and server, and the AX run-time provided the smartest marshaling of objects across the tiers on the planet. The marshaling is guaranteed to work in virtually any object graph you can instantiate. You can have client-side classes holding references to instances of server-side classes, which contains references back to other client-side objects, which references … you get the idea. All you have to do as a developer is to decorate your classes as client, server or called-from.
You don’t have to worry about any low level details like how the instances are communicating across the wire. The key word in the previous sentence is “have” – stuff will just work, but unless you are very careful you may end up creating an extremely chatty (i.e. lot of RPC calls on the wire) implementation. Recently I’ve seen two cases of well-intended changes that on the surface looked right, but both caused an explosion of RPC calls. Both were implemented by smart guys, and I wanted to point them to an article explaining the problem – and I realized that article didn’t exist. Until now.
The garbage collector (GC) AX is responsible for releasing memory consumed by object instances no longer in use. In .NET the GC is indeterministic, it runs when it “feels like” running, typically when the system has CPU capacity and is low on memory. In contrast the GC in AX is deterministic – it runs every time an object goes out of scope.
Consider this small example:
static void GCJob1(Args _args)
myServerClass = new MyServerClass();
Jobs run on the client tier, so this will create an instance of the MyServerClass and release it again. MyServerClass is a trivial class with RunOn=Server.
If we enable Client/Server trace under Tools | Options | Development, and run the job, we get:
Notice this: The client-tier reference to the server instance, is keeping the instance alive. When the reference goes out-of-scope, then the GC takes over and calls the server to free the server memory.
The GC is not just looking for instances without references – often object graphs are more complicated. To release memory that is no longer needed, the GC is looking for groups of objects without any external references – or in popular lingo: Islands. This search is potentially harmful to the performance of your application. The GC must traverse all members of any object that goes out of scope – regardless of their tier.
Let’s build out the example by introducing a client side class that is referenced by the server instance.
static void GCJob2(Args _args)
//Create client instance
myClientClass = new MyClientClass();
//Create server instance
myServerClass = new MyServerClass();
//Make server instance reference client instance
Pretty smart – but not for free!
This is the resulting RPC traffic from the above job:
Create client instance Create server instance Call Server: object: MyServerClass.new() Make server instance reference client instance Call Server: object: MyServerClass.parmMyClientClass() Call Client: set class loop dependencies Call Client: test class loop dependencies Release instances Call Server: destruct class Call Client: destruct class
Now suddenly we jumped from 2 RPC calls to 6! What happened? We met the GC!
Consider a server side class that is looping over some data, and for each, say, row, it spins up another class instance on the server to do some calculations. This is all server side, and perfect. So let’s add a client-side member to the mix.
public void run()
//Create client-side member
myClientClass = new MyClientClass();
//Loop over some data
for (i=1; i<=10; i++)
myServerHelper = new MyServerHelper();
The alarming result is 10 client calls – or one per iteration in the loop – despite the loop only contains server side logic.
Create client-side member Call Client: object: MyClientClass.new() Call Client: set class loop dependencies Loop over some data Call Client: set class loop dependencies Call Client: set class loop dependencies Call Client: set class loop dependencies Call Client: set class loop dependencies Call Client: set class loop dependencies Call Client: set class loop dependencies Call Client: set class loop dependencies Call Client: set class loop dependencies Call Client: set class loop dependencies Call Client: set class loop dependencies
The assignment inside the parm method forces the AX runtime to traverse the object graph, and the object graph contains a client side instance.
The alert reader would have recognized this as the Runbase pattern. The client side class is the operation progress bar. In Dynamics AX 2009 the operation progress bar framework regressed, as a client side reference was introduced - exposing all thousands of consumers to this specific problem. This got fixed in Dynamics AX 2012 R3.
The implementation of the GC and the supporting runtime is symmetrical on each tier – you can recognize them in action, when you come across these calls in the Trace Parser. Remember; they are always a consequence of the exercised X++ logic. I.e. something that can be addressed if required.
Call Client: set class loop dependencies Call Client: test class loop dependencies Call Client: destruct class
Call Server: set class loop dependencies Call Server: test class loop dependencies Call Server: destruct class
There is only one way of understanding the impact the GC has on your implementation: Measure it! The best tool for measurement is the Trace Parser. Alternatively, the Client/Server trace in Tools | Options | Development can be used – it will show all the RPC calls in the Message Window.
The rule-of-thumb as a developer is to avoid class members that are living on the opposite tier. This will ensure your object graphs are single tiered, and it will make the life of the runtime and the GC much simpler, and your applications equally faster.
There are situations where cross tier members seem unavoidable. However, there are techniques to avoid them, and achieve the same functional results. Take a look in the SysOperationProgress class in AX4 or AX 2012 R3 for an example.
Code samples are attached. They are provided AS-IS and confers no rights.
Business Applications communities