dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.25k stars 4.73k forks source link

The behavior of GC.Collect() in .Net8 is different with .Net Framework #109441

Closed shaoht closed 1 day ago

shaoht commented 1 day ago

Description

The unused object doesn't been released when call GC.Collect() in .Net8, but it is released in .NetFramework.

Reproduction Steps

  1. Build and run TestWeakReferenceCore.zip , show "weakRef.IsAlive: True".
  2. Build and run TestWeakReferenceFramework.zip , show "weakRef.IsAlive: False".

Expected behavior

For TestWeakReferenceCore.csproj, show "weakRef.IsAlive: False".

Actual behavior

For TestWeakReferenceCore.csproj, show "weakRef.IsAlive: True".

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

No response

hez2010 commented 1 day ago

This is caused by tiered compilation, which is used for faster startup and instrument for dynamic PGO. In tier-0 (unoptimized) code, the lifetime of object can be extended.

Tiered compilation only promotes hot methods to tier-1 (optimized), you can observe the behavior by using this code:

while (true) Test();
Console.Read();

void Test()
{
    Object obj = new Object();
    WeakReference weakRef = new WeakReference(obj);
    obj = null;
    GC.Collect();

    Console.WriteLine($"weakRef.IsAlive: {weakRef.IsAlive.ToString()}");
}

If you want to turn off tiered compilation, you can add the following lines to your project file:

<PropertyGroup>
    <TieredCompilation>false</TieredCompilation>
</PropertyGroup>
huoyaoyuan commented 1 day ago

Or a simpler reproduction: use the original code snippet, run with <TieredCompilation>false</TieredCompilation> AND Release configuration, it prints "weakRef.IsAlive: False".

Closing as answered.