Thoughts on memory leakage in task. Run mentioned by the boss of fine code farmer

Time:2021-9-17

1: Background

1. Tell a story

During this period of time, the project was delayed and the overtime was severe. The blog stopped a little, but it still had to continue the technical output! The garden has been very lively recently. The leader of exquisite yard farmer shared three articles:

The core code is as follows:

class Program
    {
        static void Main(string[] args)
        {
            Test();
            Console.ReadLine();
        }

        static void Test()
        {
            var myClass = new MyClass();

            myClass.Foo();
        }
    }

    public class MyClass
    {
        private int _id = 10;

        public Task Foo()
        {
            return Task.Run(() =>
            {
                Console.WriteLine($"Task.Run is executing with ID {_id}");
            });
        }
    }

To the effect that:Test()After the method is executed, MyClass should have been destroyed. It is found thatFoo()Method references_ ID, which causes the GC to give up recycling of MyClass, resulting in memory leakage.

If my understanding is wrong, please help correct it. It’s very interesting. The comment area is also lively. On the whole, I find that there are still many friends rightclosure, Memory leak,GCThe cognition of such concepts is vague. As a technology blogger, I have to rub some heat. In this article, I’m going to elaborate my cognition from these three aspects, and then we’ll look back at the article of the exquisite boss.

2: Cognition of closures

1. What is closure

I first came into contact with the concept of closure in JS. As for the concept of closure, people who know it naturally understand it. Those who don’t understand it have to scratch their heads. I’m going to start with the code instead of the concept. I’ll help you sort it out and look at the core code first:

public class MyClass
    {
        private int _id = 10;

        public Task Foo()
        {
            return Task.Run(() =>
            {
                Console.WriteLine($"Task.Run is executing with ID {_id}");
            });
        }
    }

I find that many people are confused about the task in the task. Run delegate_ ID, because it takes the ID in MyClass_ ID, it seems to realize time-space travel. In fact, it’s very simple to think about it carefully. It should be taken in the task. Run delegationMyClass._id, you must pass the this pointer of MyClass itself as a parameter to the delegate. Now that you have this, what value can’t be taken out??? Unfortunately, run does not accept any object parameters, so the pseudo code is as follows:

public Task Foo()
        {
            return Task.Run((obj) =>
            {
                var self = obj as MyClass;

                Console.WriteLine($"Task.Run is executing with ID {self._id}");
            },this);
        }

I believe everyone can see the above code clearly. Some friends have nothing to say. Why are you right??? It doesn’t matter. I’ll let you see from WinDbg…

2. Use WinDbg to verify

It’s actually very simple to verify. Use WinDbg in this statementConsole.WriteLine($"Task.Run is executing with ID {_id}");Put a breakpoint on the. After hitting it, just look at the parameter list of this method.

This code is on line 35 of my file, using the command!bpmd Program.cs:35Set breakpoints.

0:000> !bpmd Program.cs:35
0:000> g
JITTED ConsoleApp4!ConsoleApp4.MyClass.b__1_0()
Setting breakpoint: bp 00007FF83B2C4480 [ConsoleApp4.MyClass.b__1_0()]
Breakpoint 0 hit
00007ff8`3b2c4480 55              push    rbp

aboveb__1_0()Method is the so-called delegate method, which can be used next!clrstack -pView the list of parameters for this method.

0:009> !clrstack -p
OS Thread Id: 0x964c (9)
        Child SP               IP Call Site
000000BF6DB7EF58 00007ff83b2c4480 ConsoleApp4.MyClass.b__1_0() [E:\net5\ConsoleApp1\ConsoleApp4\Program.cs @ 34]
    PARAMETERS:
        this () = 0x0000025c26f8ac60

You can see that this method has a parameter this, and the address is:0x0000025c26f8ac60, you can use!do 0x0000025c26f8ac60Try printing it and see what it is?

0:009> !do 0x0000025c26f8ac60
Name:        ConsoleApp4.MyClass
MethodTable: 00007ff83b383548
EEClass:     00007ff83b3926b8
Size:        24(0x18) bytes
File:        E:\net5\ConsoleApp1\ConsoleApp4\bin\Debug\netcoreapp3.1\ConsoleApp4.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ff83b28b1f0  4000001        8         System.Int32  1 instance               10 _id

Observe the output above, ha ha, as expected,0x0000025c26f8ac60namelyConsoleApp4.MyClass, do you have a new understanding of closures now???

2: Understanding of memory leakage

1. What is a memory leak

There is a phrase in English calledOut of Control, yes, it’s out of control. If you want to release it, you can onlySuicide attackFor example, kill the process and shut down the machine.

Well, back to this example, the code is as follows:

static void Test()
        {
            var myClass = new MyClass();

            myClass.Foo();
        }

After the test method is executed, the reference address on the stack of MyClass will be erased. Interestingly, at this timeTask.RunThe delegate method in has certainly not been thread scheduled. I find that many people can’t figure it out in this area, thinking thatMemory leakYes. Right

If you understand what I said in the last section, it’s easy to understand. Hey, I haven’t drawn a picture for analysis for a long time. I’m making an exception.

图片名称

It can be clearly seen that when the execution is completedmyClass.Foo();After the statement, MyClass on the heap is referenced in two places. After the test method is executed,A referenceWill be erased, but there’s stillB referenceYes, so no matter how you GC at this time, MyClass on the heap will not be recycled. If this is the caseMemory leakIf

It’s still that sentence. There’s no basis for empty words. I have to show evidence and talk to WinDbg.

2. Use WinDbg to find B reference

To verify the existence of B reference, the idea is very simple. Let the anonymous delegate method not exit, and then go to the managed heap to find out who MyClass is still referenced by. Next, modify the code slightly.

class Program
    {
        static void Main(string[] args)
        {
            Test();

            Console. Writeline ("all main threads have been executed!");
            Console.ReadLine();  
        }

        static void Test()
        {
            var myClass = new MyClass();

            myClass.Foo();
        }
    }

    public class MyClass
    {
        private int _id = 10;

        public Task Foo()
        {
            return Task.Run(() =>
            {
                Console.WriteLine($"Task.Run is executing with ID {_id}");

                Thread.Sleep(int.MaxValue);   // Deliberately do not let the method exit
            });
        }
    }

use!dumpheap -stat -type MyClassView the MyClass instance on the heap, and then use!gcrootJust view its reference chain,

0:000> !dumpheap -stat -type MyClass
Statistics:
              MT    Count    TotalSize Class Name
00007ff839d23548        1           24 ConsoleApp4.MyClass
Total 1 objects
0:000> !DumpHeap /d -mt 00007ff839d23548
         Address               MT     Size
00000284e248ac90 00007ff839d23548       24     
0:000> !gcroot 00000284e248ac90
Thread 4eb0:
    0000009CD68FED60 00007FF839C646A6 ConsoleApp4.MyClass.b__1_0() [E:\net5\ConsoleApp1\ConsoleApp4\Program.cs @ 39]
        rbp+10: 0000009cd68feda0
            ->  00000284E248AC90 ConsoleApp4.MyClass

As expected, the reference to MyClass is changingb__1_0()Method, which verifies the existence of the B reference.

3: Cognition of GC

In addition to the large object heap, small objects mainly use the old method of the Three-Generation mechanism. There is nothing to say. However, it should be noted that GC will not be recycled at any time. After all, workstation mode GC has a memory size of 256M by default on 64 bit machines, which will be allocatedGeneration 0 + generation 1, it is neither small nor small, as shown in the following figure:

In fact, what I want to express is that even if there areA,BIn fact, 99% of the two references will be recycled in the same generation, such as generation 0.

It’s been more than ten minutes now. Can you see if the address of MyClass (00000284e248ac90) has been sent to the first generation? use!eeheap -gcType out the address field of the managed heap.

0:000> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x00000284E2481030
generation 1 starts at 0x00000284E2481018
generation 2 starts at 0x00000284E2481000

It can be seen that even after more than ten minutes, MyClass (00000284e248ac90) is still on the generation 0 heap.

3: Summary

Well, these three concepts:closure, Memory leak,GCAlmost finished the introduction. I don’t know if I can solve everyone’s doubts. Finally, thank youExquisite bossWonderful blog post.

More high quality dry goods: see my GitHub:dotnetfly

图片名称

Recommended Today

Swift advanced (XV) extension

The extension in swift is somewhat similar to the category in OC Extension can beenumeration、structural morphology、class、agreementAdd new features□ you can add methods, calculation attributes, subscripts, (convenient) initializers, nested types, protocols, etc What extensions can’t do:□ original functions cannot be overwritten□ you cannot add storage attributes or add attribute observers to existing attributes□ cannot add parent […]