Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GC does not clean up memory allocated in same method #11152

Closed
ayende opened this issue Sep 27, 2018 · 6 comments
Closed

GC does not clean up memory allocated in same method #11152

ayende opened this issue Sep 27, 2018 · 6 comments
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI question Answer questions and provide assistance, not an issue with source code or documentation.

Comments

@ayende
Copy link
Contributor

ayende commented Sep 27, 2018

This only happens in debug builds, when using release builds, the code below works properly.
During debug build, even though we have cleared the reference to b, the GC still maintains that it is tracked.
In fact, if we break into this using WinDBG, we can see that there is such a root holding the value, but I can't figure out where it is coming from.

I'm aware of the release / debug GC behaviors with respect to not eagerly clearing out variables that are still in scope, but in this case, we are explicitly setting the value to null and invoking the GC to clean things up.

using System;

namespace ConsoleApp15
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine(GC.GetTotalMemory(true));
            WeakReference f = null;

            byte[] b;
            b = new byte[1024 * 1024 * 64];
            f = new WeakReference(b);
            b = null;

            for (var i = 0; i < 3; i++)
            {
                GC.WaitForPendingFinalizers();
                GC.Collect(2);
            }

            Console.WriteLine(GC.GetTotalMemory(true));
            Console.WriteLine(f.IsAlive);
        }
    }
}

This actually showed up for us when we noticed high memory utilization in a scenario where it shouldn't. This was using a debug build, and while we cleared up the field that was holding the value, we had the same experience. Under debug, there is something else that holds a reference to the value.

@jkotas
Copy link
Member

jkotas commented Sep 27, 2018

This is same issue as:

https://github.com/dotnet/coreclr/issues/15207
https://github.com/dotnet/coreclr/issues/5826#issuecomment-226574611

The JIT can produce temporary local variables. These temporary local variables are outside your control and they may result into the reference lifetime getting extended. The likelihood of this happening is much higher with optimizations off. This issue can happen with optimizations on as well for more complex methods.

@ayende
Copy link
Contributor Author

ayende commented Sep 27, 2018

A slightly adjusted version is here:
https://gist.github.com/ayende/3788b7cebc63c1cdd7ab614186e6ca78

This simulates the original issue we had, a long running loop that holds state and clears it.
But the value wasn't cleared and so the value we held in memory over what we expected.
That led to us holding an entire object graph.

It doesn't reproduce in release, but I'm worried if something like this can happen on release?

Also, WinDBG gives inconsistent results:
https://gist.github.com/ayende/535a857ffa75b06f1fa2f1642eaf6607#file-clrstack-txt-L16

https://gist.github.com/ayende/75fd760a83252730786b5feb8faf887a

I wonder if a good choice for long running loops would be to do the inner loop work in its own method, to avoid this ?

@janvorli
Copy link
Member

A separate method would fix that reliably only if it is marked as non-inlineable.

@RussKeldorph
Copy link
Contributor

I don't think we're ready to sign the JIT up to guarantee lifetimes match their lexical scopes. In fact, we're constantly "fixing" test cases that make the same assumption about lifetimes as above. In theory, we might consider addressing particularly egregious examples that were hitting in real world code in release builds, but even then the workaround of creating a no-inilne helper method would be our recommendation most of the time.

@dotnet/jit-contrib Feel free to re-open if you see anything actionable here.

@CarolEidt
Copy link
Contributor

This has been discussed elsewhere, but another point to make is that the spec is careful to make few guarantees about when finalizers are called, specifically to provide flexibility to runtimes and code generators with regard to optimization and reporting. See in particular I.8.9.6.7 - "Since programmers might depend on finalizers to be called, the CLI should make every effort, before it shuts down, to ensure that finalizers are called" (implying that even calling them before shutdown is a "best effort" requirement).

@jkotas
Copy link
Member

jkotas commented Jan 24, 2019

This part of the spec is outdated. .NET Core does not make effort to call finalizers during shutdown.

@msftgits msftgits transferred this issue from dotnet/coreclr Jan 31, 2020
@dotnet dotnet locked as resolved and limited conversation to collaborators Dec 15, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests

5 participants