To detect potential memory-leaks in places where it already happened a lot I have work with tests that are build like the shown below. The main idea is to have an instance, not reference it any longer and let the garbage collector collect it. I would like not to focus on whether this is a good technique or not (in my particular case it did an excellent job) but I would like to focus on the following question:
The code below works perfectly on .NetFramework 4.8 but does not on .Net 5. Why?
[Test]
public void ConceptualMemoryLeakTest()
{
WeakReference weakReference;
{
object myObject = new object();
weakReference = new WeakReference(myObject);
myObject = null;
Assert.Null(myObject);
}
Assert.True(weakReference.IsAlive); // instance not collected by GC
GC.Collect();
GC.WaitForPendingFinalizers();
GC.WaitForFullGCComplete();
GC.Collect();
Assert.False(weakReference.IsAlive); // instance collected by GC
}
You can see the main idea is to work with a WeakReference
and use IsAlive
to determine whether the instance got removed by the GC. How did the rules in the new CLR (the one originating from dotnet core) change? I know that what is done here does not relay on something that is specified. Rather I just exploit the behavior I see with the CLR in NetFramework 4.8.
Do you have an idea how to get something similar again that works also with .Net 5?
CodePudding user response:
The reason is likely tiered compilation. In simple words, tiered compilation will (for some methods under some conditions) first compile crude, low optimized version of a method, and then later will prepare a better optimized version if necessary. This is enabled by default in .NET 5 (and .NET Core 3 ), but is not available in .NET 4.8.
In your case the result is your method is compiled with mentioned "quick" compilation and is not optimized enough for your code to work as you expect (that is lifetime of myObject
variable extends until the end of the method). That is the case even if you compile in Release mode with optimizations enabled, and without any debugger attached.
You can disable tiered compilation by adding:
<TieredCompilation>false</TieredCompilation>
To some <PropertyGroup>
inside csproj file for your .NET 5 project, then you'll observe the same behaviour you have in .NET 4.8 case.
Another alternative (besides moving variable to another method and returning WeakReference
from it) is to use:
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
attribute on ConceptualMemoryLeakTest
method.
CodePudding user response:
Actually with hints provided in the comments and answers I realized that moving the instance to a separate method and prevent in-lining it works:
[MethodImpl(MethodImplOptions.NoInlining)]
private WeakReference CreateWeakReference()
{
object myObject = new object();
return new WeakReference(myObject);
}
[Test]
public void ConceptualMemoryLeakTest()
{
WeakReference weakReference = CreateWeakReference();
Assert.True(weakReference.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.WaitForFullGCComplete();
GC.Collect();
Assert.False(weakReference.IsAlive);
}
This does not require
<TieredCompilation>false</TieredCompilation>