A: background

1. Tell a story

During this period of time, the project has been delayed, so I have to work overtime, so my blog has been stopped a little bit, but I still need to continue technical output! The garden is quite busy recently, the master of exquisite code farmer shared three articles:

  • Why do you want to be careful with the Task. Run [www.cnblogs.com/willick/p/1]…
  • Be careful with the Task. Run a sequel [www.cnblogs.com/willick/p/1]…
  • Be careful with the Task. To reassure the Run final [mp.weixin.qq.com/s/IMPgSsxTW…].

The core code is as follows:


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

        static void Test()
        {
            var myClass = newMyClass(); myClass.Foo(); }}public class MyClass
    {
        private int _id = 10;

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

After the Test() method was executed, myClass was supposed to be destroyed, but Foo() referred to _id, causing the GC to abandon the collection of myClass, causing a memory leak.

If my understanding is wrong, please help correct, interesting, comments area is also very busy, the overall look down and found that there are still a lot of friends of closures, memory leaks, GC with vague concepts such as cognitive, also as a technology blogger, to rub some heat 😄 😄 😄, this article I prepared from these three aspects elaborated under my cognitive, And then you go back to the article of the Exquisite Tycoon.

Two: the knowledge of closures

1. What are closures

I first came into contact with the concept of closure in JS. As for the concept of closure, people who know it will naturally understand it, but those who do not know it will have to scratch their heads. I am going to start from the code rather than the concept.


    public class MyClass
    {
        private int _id = 10;

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

The _id of task.run is the _id of MyClass, which is a time-travel method. The _id of task.run is the _id of MyClass. We must pass MyClass’s this pointer as an argument to the delegate. Unfortunately, Run does not accept any object arguments, so the pseudocode looks like this:


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

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

Copy the code

The above code I believe we all see very clearly, some friends to say, empty mouth without evidence, with what you say is right?? It doesn’t matter, I from WinDBG let you see is the truth…

2. Use WinDBG for authentication

Console.WriteLine($” task. Run is executing with ID {_id}”); Put a breakpoint on it, and then look at the argument list of the method.

This is line 35 of my file, use command! BPMD program. cs:35 Set a breakpoint.


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

Copy the code

The

b__1_0() method above is called a delegate method and can be used next! Clrstack -p Views the argument list 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 (<CLR reg>) = 0x0000025c26f8ac60

Copy the code

As you can see, this method takes a parameter this at the address 0x0000025c26F8AC60. Do 0x0000025C26F8AC60 try 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\netcoreapp31.\ConsoleApp4.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ff83b28b1f0  4000001        8         System.Int32  1 instance               10 _id

Copy the code

Consoleapp4.myclass = 0x0000025C26F8AC60 consoleApp4.myClass = consoleApp4.myClass = consoleApp4.myClass = consoleApp4.myClass = consoleApp4.myClass

Two: the understanding of memory leakage

What is a memory leak

We have an expression in English called “Out of Control”. Yes, it means Out of Control, and the only way to release it is by suicide, for example: kill the process, turn off the machine.

Ok, back to the example, the code looks like this:


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

            myClass.Foo();
        }

Copy the code

When the Test method completes, the reference address on myClass’s stack will be erased. The delegate method in task.run has not yet been scheduled. Right 🤣 🤣 🤣

If you understand what I said in the last video, it makes a lot of sense, but hey, it’s been a long time since I drew a picture.

It is clear that when myclass.foo () is executed; After the Test method is executed, the A reference will be erased, but the B reference will still exist, so the MyClass on the heap will not be reclaimed no matter how you GC it. If this is A memory leak, the MyClass on the heap will not be reclaimed.

Or that sentence, empty words without proof, I have to take out the evidence, winDBG talk.

2. Use WinDBg to find references to B

To verify the existence of a reference to B, the idea is simple, let the anonymous delegate method not exit, then go to the managed heap to find out who is still referencing MyClass, and modify the code slightly.


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

            Console.WriteLine("Main thread complete!");
            Console.ReadLine();  
        }

        static void Test()
        {
            var myClass = newMyClass(); 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 keep the method from exiting}); }}Copy the code

Use! Dumpheap-stat-type MyClass = dumpheap-stat-type MyClass Gcroot just looks at its chain of references,


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.<Foo>b__1_0() [E:\net5\ConsoleApp1\ConsoleApp4\Program.cs @ 39]
        rbp+10: 0000009cd68feda0
            ->  00000284E248AC90 ConsoleApp4.MyClass
Copy the code

Sure enough, the MyClass reference is in the

b__1_0() method, which verifies the existence of the B reference.

Iii. Cognition of GC

In addition to the large object heap, the small object is still the old method of the third generation mechanism, there is nothing to say, but it is important to note that the GC is not always out of collection, after all, the workstation mode GC on the 64 bit machine default 256MB memory size, 256 MB allocated to generation 0 + 1. Small is not small, as shown below:

What I mean by this is that even if there are references A and B, 99 percent of the time they will be recycled in the same generation, say generation 0.

MyClass address (00000284E248AC90) has been sent to generation 1. Use! Eeheap-gc prints out the managed heap address segment.


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

Copy the code

As you can see, MyClass(00000284E248AC90) is currently on generation 0 heap, even after more than ten minutes.

Three:

Ok, these three concepts: closure, memory leakage,GC almost finished introduction, I don’t know if you can solve the puzzle, finally thanks to the refined big guy wonderful blog.

For more high-quality dry goods: See my GitHub:dotnetfly