DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Deployment
  4. Challenge: The Loop That Leaks (Answer)

Challenge: The Loop That Leaks (Answer)

Let's explore the answer to yesterday's code ''riddle'' on the loop that leaks in this C# code snippet.

Oren Eini user avatar by
Oren Eini
·
Oct. 05, 18 · Analysis
Like (1)
Save
Tweet
Share
3.21K Views

Join the DZone community and get the full member experience.

Join For Free

In my previous post, I asked about the following code and what its output will be:

class Program
{
    static ManualResetEvent _produce = new ManualResetEvent(false);
    static ManualResetEvent _consume = new ManualResetEvent(false);
    private static void Run()
    {
        while (true)
        {
            _produce.WaitOne();
            _produce.Reset();

            var b = new byte[1024 * 1024 * 128];
            MD5.Create().ComputeHash(b);
            b = null;
            Console.WriteLine("Done");
            _consume.Set();
        }
    }

    static void Main(string[] args)
    {
        new Thread(Run)
        {
            IsBackground = true,
        }.Start();

        while (true)
        {
            _produce.Set();
            _consume.WaitOne();
            _consume.Reset();
            GC.Collect(2);
            GC.WaitForPendingFinalizers();
            Console.WriteLine(GC.GetTotalMemory(true));
            Console.ReadLine();
        }
    }
}


As it turns out, this code will output two different numbers:

  • On Debug – 134,284,904
  • On Release – 66,896

The behavior is consistent between these two modes.

I was pretty sure that I knew what was going on, but I asked to verify. You can read the GitHub issue if you want a spoiler.

I attached to the running program in WinDBG and issued the following command:

0:008> !dumpheap -stat
PDB symbol for clr.dll not loaded
Statistics:
              MT    Count    TotalSize Class Name
00007ffd2850a5b0        1           24 System.Collections.Generic.GenericEqualityComparer`1[[System.Int32, System.Private.CoreLib]]
00007ffd284f72a0        1           24 System.Collections.Generic.NonRandomizedStringEqualityComparer

// redacted

00007ffd284b41d8       18         1064 System.String[]
00007ffd284b6a10        6         2840 System.Char[]
00007ffd284b3878        6        17656 System.Object[]
00007ffd284cfba0      135        34874 System.String
000001f81abfe420       30        93806      Free
00007ffd284b6850        9    134221823 System.Byte[]
Total 347 objects


We care about the last line. In particular, we can see that all the memory is indeed in the byte array, as expected.

Next, let’s dump the actual instances that take so much space:

0:008> !DumpHeap /d -mt 00007ffd284b6850
         Address               MT     Size
000001f81c7521b8 00007ffd284b6850       40     
000001f81c7521e0 00007ffd284b6850       40     
000001f81c752408 00007ffd284b6850       27     
000001f81c752570 00007ffd284b6850       26     
000001f81c752590 00007ffd284b6850       26     
000001f81c752bc0 00007ffd284b6850     3608     
000001f81c7539d8 00007ffd284b6850       24     
000001f81c754290 00007ffd284b6850      280     
000001f8350a1020 00007ffd284b6850 134217752     

Statistics:
              MT    Count    TotalSize Class Name
00007ffd284b6850        9    134221823 System.Byte[]
Total 9 objects


There is one large instance here that we care about. Let’s see what is holding on to this fellow, shall we?

0:008> !gcroot 000001f8350a1020
Thread 2f30:
    00000011157ff150 00007ffcd94822c5 ConsoleApp18.Program.Run() [C:\Users\ayende\source\repos\ConsoleApp18\ConsoleApp18\Program.cs @ 15]
        rbp-28: 00000011157ff198
            ->  000001f8350a1020 System.Byte[]

Found 1 unique roots (run '!gcroot -all' to see all roots).


It looks like we have a reference from a local variable. Let’s see if we can verify that, shall we? We will use the clrstack command and ask it to give us the parameters and local variables, like so:

0:007> !clrstack -p -a
OS Thread Id: 0x2f30 (7)
        Child SP               IP Call Site
00000011157ff008 00007ffd8135aa44 [HelperMethodFrame_1OBJ: 00000011157ff008] System.Threading.WaitHandle.WaitOneNative(System.Runtime.InteropServices.SafeHandle, UInt32, Boolean, Boolean)
00000011157ff120 00007ffd2820715b System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle, Int64, Boolean, Boolean) [E:\A\_work\164\s\src\mscorlib\src\System\Threading\WaitHandle.cs @ 199]
    PARAMETERS:
        waitableSafeHandle = <no data>
        millisecondsTimeout = <no data>
        hasThreadAffinity = <no data>
        exitContext = <no data>
    LOCALS:
        <no data>

00000011157ff150 00007ffcd94822c5 ConsoleApp18.Program.Run() [C:\Users\ayende\source\repos\ConsoleApp18\ConsoleApp18\Program.cs @ 15]
    LOCALS:
        0x00000011157ff1b0 = 0x0000000000000000
        0x00000011157ff1ac = 0x0000000000000001

00000011157ff1d0 00007ffd76487080 System.Threading.Thread.ThreadMain_ThreadStart() [E:\A\_work\110\s\corefx\src\System.Threading.Thread\src\System\Threading\Thread.cs @ 93]
    PARAMETERS:
        this = <no data>
    LOCALS:
        <no data>

00000011157ff200 00007ffd280ecda9 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) [E:\A\_work\164\s\src\mscorlib\shared\System\Threading\ExecutionContext.cs @ 167]
    PARAMETERS:
        executionContext = <no data>
        callback = <no data>
        state = <no data>
    LOCALS:
        <no data>
        0x00000011157ff248 = 0x000001f81c7510e0
        <no data>
        0x00000011157ff240 = 0x0000000000000000
        0x00000011157ff238 = 0x0000000000000000
        0x00000011157ff230 = 0x0000000000000000
        <no data>
        <no data>
        <no data>
        <no data>
        <no data>

00000011157ff468 00007ffd38f53ba3 [GCFrame: 00000011157ff468] 
00000011157ff6e0 00007ffd38f53ba3 [DebuggerU2MCatchHandlerFrame: 00000011157ff6e0] 


The interesting line is 16, which shows:

image

In other words, here is the local variable, and it is set to null. What is going on? And why don’t we see the same behavior on release mode?

As mentioned in the issue, the problem is that the JIT introduce a temporary local variable here, which the GC is obviously aware of, but WinDBG is not. This cause the program to hold on to the value for a longer period of time than expected.

In general, this should only be a problem if you have a long running loop. In fact, we do in some case, and in debug mode, that actually caused our memory utilization to go through the roof and led to this investigation.

In release mode, these temporary variables are rarer (but can still happen, it seems).

Release (agency) Debug (command) Memory (storage engine) Command (computing) GitHub LEd garbage collection Space (architecture)

Published at DZone with permission of Oren Eini, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Better Performance and Security by Monitoring Logs, Metrics, and More
  • Bye Bye, Regular Dev [Comic]
  • Kubernetes vs Docker: Differences Explained
  • Differences Between Site Reliability Engineer vs. Software Engineer vs. Cloud Engineer vs. DevOps Engineer

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: