Thread与ThreadPool使用的时候在内存里对象是如何分布的呢?

今天我们就从内存堆的角度分析下两者。

先上小白鼠代码:




Thread与ThreadPool的内存之战_.net

static void Main(string[] args)

        {

            for (int i = 0; i < 30; i++)

            {

                Thread t = new Thread(new ThreadStart(ThreadProc));

                t.Name = "Overred_" + i;

                t.Start();

            }

            Console.Read();

        }

        static void ThreadProc()

        {

            try

            {

                for (int i = 0; i < 10; i++)

                {

                     Console.WriteLine("{0}  Value:{1}",Thread.CurrentThread.Name,i);

                }

               

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex.Message);

            }

        }

Thread与ThreadPool的内存之战_i++_02

以上代码非常简单,就是循环启动30个线程去执行同一个方法ThreadProc(),然后打印出结果。
现在提出问题1:当Main里的30个线程都把ThreadProc()方法执行完毕后,这些Threads是自动消亡还是被GC回收,还是变成DeadThread?
好,拿出我们的看家工具windbg,来debug一把。
首先启动我们的程序,然后打开windbg,然后F6,Attach我们的exe
1,加载mscorwks(.net 2.0或者以上)



0:003> .loadby sos mscorwks 


2,查看该程序的线程情况



Thread与ThreadPool的内存之战_i++_03

0:003> !Threads

*** ERROR: Symbol file could not be found.  Defaulted to export symbols for 

C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll - 

PDB symbol for mscorwks.dll not loaded

ThreadCount: 32UnstartedThread: 0

BackgroundThread: 1

PendingThread: 0

DeadThread: 30Hosted Runtime: no

                                      PreEmptive   GC Alloc           Lock

       ID OSID ThreadOBJ    State     GC       Context       Domain   Count APT Exception

   0    1 25e4 00518858      a020 Enabled  013f878c:013f9fe8 00514818     1 MTA

   2    2 24b8 00526f20      b220 Enabled  00000000:00000000 00514818     0 MTA (Finalizer)

XXXX    3    0 00533028      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    4    0 00536858      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    5    0 005385c8      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    6    0 005393d0      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    7    0 00534fd8      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    8    0 0053a5c0      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    9    0 0053b3c8      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    a    0 0053bfc0      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    b    0 0053eba8      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    c    0 00543370      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    d    0 00543b38      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    e    0 00544700      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    f    0 00544ec8      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   10    0 00545690      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   11    0 00545ee0      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   12    0 005466c0      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   13    0 00546a88      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   14    0 00546e50      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   15    0 00547218      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   16    0 005475e0      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   17    0 005479a8      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   18    0 00547d70      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   19    0 00548138      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   1a    0 00548500      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   1b    0 005488c8      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   1c    0 00548c90      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   1d    0 00549058      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   1e    0 00549420      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   1f    0 005497e8      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   20    0 00549bb0      9820 Enabled  00000000:00000000 00514818     0 Ukn

Thread与ThreadPool的内存之战_i++_04


 

看红色加粗部分,我们总共有32个线程,而DeadThread为30个(其他2个为程序自身所有,其中一个BackgroundThread),先告诉你这30个死线程正式我们循环创建的线程,可以回答我提的第一个问题拉,没错,他们统统死拉,而且不会醒来,还占地方(不是永远占地方,待会我们用GC手动让它们消亡)。

3,然后我们继续看看内存堆上它们这些坏家伙如何分布:




Thread与ThreadPool的内存之战_.net_05

0:003> !DumpHeap -type System.Threading -stat

total 155 objects

Statistics:

      MT    Count    TotalSize Class Name

79108930        1           32 System.Threading.ContextCallback

790fe284        2          144 System.Threading.ThreadAbortException

79124b74       30          600 System.Threading.ThreadHelper

79104de8       31         1116 System.Threading.ExecutionContext790fe704       31         1736 System.Threading.Thread791249e8       60         1920 System.Threading.ThreadStart

Total 155 objects

Thread与ThreadPool的内存之战_.net_06


 


红色部分,31个Thread,对应着31个Context,每个线程在windows底层都是一个内核对象和一个栈空间,内核对象存放一些线程的统计信息,比如计数器以及一个上下文,就是我上次执行到那里等。而栈空间则是用来存放线程参数等。


 


4,我们来具体看下这些Thread们的MethodTable




Thread与ThreadPool的内存之战_i++_07

0:003> !DumpHeap -MT 790fe704 

 Address       MT     Size

013c1708 790fe704       56     

013c178c 790fe704       56     

013c235c 790fe704       56     

013c2474 790fe704       56     

013c258c 790fe704       56     

013c26a4 790fe704       56     

013c27bc 790fe704       56     

013c28d4 790fe704       56     

013c29ec 790fe704       56     

013c2b04 790fe704       56     

013c2c1c 790fe704       56     

013c2d34 790fe704       56     

013c2e54 790fe704       56     

013c2f74 790fe704       56     

013c3094 790fe704       56     

013c31b4 790fe704       56     

013c32d4 790fe704       56     

013c33f4 790fe704       56     

013c3514 790fe704       56     

013c3634 790fe704       56     

013c3754 790fe704       56     

013c3874 790fe704       56     

013c3994 790fe704       56     

013c3ab4 790fe704       56     

013c3bd4 790fe704       56     

013c3cf4 790fe704       56     

013c3e14 790fe704       56     

013c3f34 790fe704       56     

013f8084 790fe704       56     

013f81a4 790fe704       56     

013f82c4 790fe704       56     

total 31 objects

Statistics:

      MT    Count    TotalSize Class Name

790fe704       31         1736 System.Threading.Thread

Total 31 objects

Thread与ThreadPool的内存之战_5e_08

 

5,随便拿一个线程的Address来看看到底是谁占着我们的Thread而不让我们的GC回收掉




Thread与ThreadPool的内存之战_microsoft_09

0:003> !GCRoot 013c3bd4

Note: Roots found on stacks may be false positives. Run "!help gcroot" for

more info.

Scan Thread 0 OSTHread 25e4

Scan Thread 2 OSTHread 24b8

DOMAIN(00514818):HANDLE(WeakSh):241298:Root:013c3bd4(System.Threading.Thread)

Thread与ThreadPool的内存之战_5e_10


结果另我们很失望,他自己就是根,并没被其他任何对象所引用,什么情况下会出现此情况呢?我们先来看看对象在内存中分布的几种方式,我们只需在windbg里执行如下命令则知:




Thread与ThreadPool的内存之战_5e_11

0:003> !Help gcroot

-------------------------------------------------------------------------------

!GCRoot [-nostacks] <Object address>

!GCRoot looks for references (or roots) to an object. These can exist in four

places:

   1. On the stack

   2. Within a GC Handle

   3. In an object ready for finalization

   4. As a member of an object found in 1, 2 or 3 above.

First, all stacks will be searched for roots, then handle tables, and finally

the freachable queue of the finalizer. Some caution about the stack roots: 

!GCRoot doesn't attempt to determine if a stack root it encountered is valid 

or is old (discarded) data. You would have to use !CLRStack and !U to 

disassemble the frame that the local or argument value belongs to in order to 

determine if it is still in use.

Because people often want to restrict the search to gc handles and freachable

objects, there is a -nostacks option.

Thread与ThreadPool的内存之战_.net_12


 


windbg已经很清楚的告诉我们,

一个对象可以

1,在栈上

2,在一个GCHandle里(可以执行!GCHandles命令查看)

3,在FinalizeQueue里

4,是一个对象的成员

难道对象就必定在以上的“四行”之中吗?答案是不一定,还有个Gchandleleaks,就是你在内存里看不到这个Handle,它已经leak。(这种也算在GCHandle里吧)。


回头我们接着说他自己没被其他任何对象所引用,自己就是个根,但是GC却不搭理它,为何?那就是他在GCHandle里,


 




Thread与ThreadPool的内存之战_.net_13

0:003> !GCHandles

GC Handle Statistics:

Strong Handles: 14

Pinned Handles: 4

Async Pinned Handles: 0

Ref Count Handles: 0

Weak Long Handles: 0

Weak Short Handles: 31

Other Handles: 0

Statistics:

      MT    Count    TotalSize Class Name

790fd0f0        1           12 System.Object

790fcc48        1           24 System.Reflection.Assembly

790feba4        1           28 System.SharedStatics

790fe17c        1           72 System.ExecutionEngineException

790fe0e0        1           72 System.StackOverflowException

790fe044        1           72 System.OutOfMemoryException

790fed00        1          100 System.AppDomain

79100a18        4          144 System.Security.PermissionSet

790fe284        2          144 System.Threading.ThreadAbortException

790fe704       32         1792 System.Threading.Thread

7912d8f8        4         8736 System.Object[]

Total 49 objects

Thread与ThreadPool的内存之战_microsoft_14


而且在FinalizeQueue里也有它的踪影:


 




Thread与ThreadPool的内存之战_.net_15

0:003> !FinalizeQueue

SyncBlocks to be cleaned up: 0

MTA Interfaces to be released: 0

STA Interfaces to be released: 0

----------------------------------

generation 0 has 35 finalizable objects (00526658->005266e4)

generation 1 has 0 finalizable objects (00526658->00526658)

generation 2 has 0 finalizable objects (00526658->00526658)

Ready for finalization 0 objects (005266e4->005266e4)

Statistics:

      MT    Count    TotalSize Class Name

791037c0        1           20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle

79103764        1           20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle

79101444        2           40 Microsoft.Win32.SafeHandles.SafeFileHandle

790fe704       31         1736 System.Threading.Thread

Total 35 objects

Thread与ThreadPool的内存之战_5e_16


 


下面就来解释下什么才可以在FinalizeQueue里出现呢?答案就是有身份的人,很有身份的人,享受特殊待遇的哦!

啥身份,就是自身实现拉析构函数。


啥待遇,就是GC两次才有可能把他们部分清理掉!为啥部分,是我们不知道windows到底何时去把所有的清理掉(赖皮阿)

具体原理大家可以看.net框架去,我这里不多说。


 


说到此,也就找到我们当初30个彪形大汉为啥赖着不走的原因拉,是在0代的第一次GC时候,他们被放进FinalizeQueue,等着第二次GC他们部分才会从内存堆上消亡。
为证明我们的观点,我们可以修改程序为 :




Thread与ThreadPool的内存之战_microsoft_17

static void Main(string[] args)

        {

            for (int i = 0; i < 30; i++)

            {

                Thread t = new Thread(new ThreadStart(ThreadProc));

                t.Name = "Overred_" + i;

                t.Start();

            }

            GC.Collect();

            GC.Collect();

            Console.Read();

        }

Thread与ThreadPool的内存之战_microsoft_18

首先声明一点就是当我们调用一次GC.Collect();时,并不是执行一次垃圾收集,只是告诉系统我要强制进行垃圾收集,系统听到这个命令后乖不乖那就不一定拉。

当我们用Reflector查看mscorlib对Thread实现的使用也会发现他实现拉析构:



Thread与ThreadPool的内存之战_5e_19

    ~Thread()

    {

        this.InternalFinalize();

    }

Thread与ThreadPool的内存之战_microsoft_20


来个虎头蛇尾吧,当我们把小白鼠程序使用ThreadPool修改为:




Thread与ThreadPool的内存之战_i++_21

 static void Main(string[] args)

        {

            for (int i = 0; i < 30; i++)

            {

                ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));

            }

            Console.Read();

        }

        static  void ThreadProc(object o)

        {

            try

            {

                for (int i = 0; i < 10; i++)

                {

                     Console.WriteLine(" Value:{0}",i);

                }

               

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex.Message);

            }

        }

Thread与ThreadPool的内存之战_i++_22


 


再用windbg查看线程时则为:



Thread与ThreadPool的内存之战_i++_23

0:006> !Threads

*** ERROR: Symbol file could not be found.  Defaulted to export symbols 

for C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll - 

PDB symbol for mscorwks.dll not loaded

ThreadCount: 4

UnstartedThread: 0

BackgroundThread: 3

PendingThread: 0

DeadThread: 0

Thread与ThreadPool的内存之战_microsoft_24


而FinalizeQueue则为:



Thread与ThreadPool的内存之战_.net_25

0:006> !FinalizeQueue

SyncBlocks to be cleaned up: 0

MTA Interfaces to be released: 0

STA Interfaces to be released: 0

----------------------------------

generation 0 has 7 finalizable objects (00266658->00266674)

generation 1 has 0 finalizable objects (00266658->00266658)

generation 2 has 0 finalizable objects (00266658->00266658)

Ready for finalization 0 objects (00266674->00266674)

Statistics:

      MT    Count    TotalSize Class Name

791037c0        1           20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle

79103764        1           20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle

79101444        2           40 Microsoft.Win32.SafeHandles.SafeFileHandle

790fe704        3          168 System.Threading.Thread

Total 7 objects

Thread与ThreadPool的内存之战_microsoft_26


那现在又出现问题拉,既然ThreadPool这么好,那我们为啥还使用Thread呢?这个问题就是ThreadPool有个GetMaxThreads,可以通过GetMaxThreads(out int workerThreads, out int completionPortThreads);方法获取到,如果线程池满拉,则会死锁更严重!

另:ThreadPool都为后台线程。

究竟使用那个,根据情况而定,理解拉内在的东西,一切表象就简单拉。

OK,到此吧。。。

希望本文能对你有所帮助,谢谢!


 


 



Thread与ThreadPool使用的时候在内存里对象是如何分布的呢?

今天我们就从内存堆的角度分析下两者。

先上小白鼠代码:




Thread与ThreadPool的内存之战_microsoft_27

static void Main(string[] args)

        {

            for (int i = 0; i < 30; i++)

            {

                Thread t = new Thread(new ThreadStart(ThreadProc));

                t.Name = "Overred_" + i;

                t.Start();

            }

            Console.Read();

        }

        static void ThreadProc()

        {

            try

            {

                for (int i = 0; i < 10; i++)

                {

                     Console.WriteLine("{0}  Value:{1}",Thread.CurrentThread.Name,i);

                }

               

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex.Message);

            }

        }

Thread与ThreadPool的内存之战_3c_28

以上代码非常简单,就是循环启动30个线程去执行同一个方法ThreadProc(),然后打印出结果。
现在提出问题1:当Main里的30个线程都把ThreadProc()方法执行完毕后,这些Threads是自动消亡还是被GC回收,还是变成DeadThread?
好,拿出我们的看家工具windbg,来debug一把。
首先启动我们的程序,然后打开windbg,然后F6,Attach我们的exe
1,加载mscorwks(.net 2.0或者以上)



0:003> .loadby sos mscorwks 


2,查看该程序的线程情况



Thread与ThreadPool的内存之战_microsoft_29

0:003> !Threads

*** ERROR: Symbol file could not be found.  Defaulted to export symbols for 

C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll - 

PDB symbol for mscorwks.dll not loaded

ThreadCount: 32UnstartedThread: 0

BackgroundThread: 1

PendingThread: 0

DeadThread: 30Hosted Runtime: no

                                      PreEmptive   GC Alloc           Lock

       ID OSID ThreadOBJ    State     GC       Context       Domain   Count APT Exception

   0    1 25e4 00518858      a020 Enabled  013f878c:013f9fe8 00514818     1 MTA

   2    2 24b8 00526f20      b220 Enabled  00000000:00000000 00514818     0 MTA (Finalizer)

XXXX    3    0 00533028      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    4    0 00536858      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    5    0 005385c8      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    6    0 005393d0      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    7    0 00534fd8      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    8    0 0053a5c0      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    9    0 0053b3c8      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    a    0 0053bfc0      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    b    0 0053eba8      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    c    0 00543370      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    d    0 00543b38      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    e    0 00544700      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX    f    0 00544ec8      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   10    0 00545690      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   11    0 00545ee0      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   12    0 005466c0      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   13    0 00546a88      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   14    0 00546e50      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   15    0 00547218      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   16    0 005475e0      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   17    0 005479a8      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   18    0 00547d70      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   19    0 00548138      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   1a    0 00548500      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   1b    0 005488c8      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   1c    0 00548c90      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   1d    0 00549058      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   1e    0 00549420      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   1f    0 005497e8      9820 Enabled  00000000:00000000 00514818     0 Ukn

XXXX   20    0 00549bb0      9820 Enabled  00000000:00000000 00514818     0 Ukn

Thread与ThreadPool的内存之战_i++_30


 

看红色加粗部分,我们总共有32个线程,而DeadThread为30个(其他2个为程序自身所有,其中一个BackgroundThread),先告诉你这30个死线程正式我们循环创建的线程,可以回答我提的第一个问题拉,没错,他们统统死拉,而且不会醒来,还占地方(不是永远占地方,待会我们用GC手动让它们消亡)。

3,然后我们继续看看内存堆上它们这些坏家伙如何分布:




Thread与ThreadPool的内存之战_i++_31

0:003> !DumpHeap -type System.Threading -stat

total 155 objects

Statistics:

      MT    Count    TotalSize Class Name

79108930        1           32 System.Threading.ContextCallback

790fe284        2          144 System.Threading.ThreadAbortException

79124b74       30          600 System.Threading.ThreadHelper

79104de8       31         1116 System.Threading.ExecutionContext790fe704       31         1736 System.Threading.Thread791249e8       60         1920 System.Threading.ThreadStart

Total 155 objects

Thread与ThreadPool的内存之战_microsoft_32


 


红色部分,31个Thread,对应着31个Context,每个线程在windows底层都是一个内核对象和一个栈空间,内核对象存放一些线程的统计信息,比如计数器以及一个上下文,就是我上次执行到那里等。而栈空间则是用来存放线程参数等。


 


4,我们来具体看下这些Thread们的MethodTable




Thread与ThreadPool的内存之战_.net_33

0:003> !DumpHeap -MT 790fe704 

 Address       MT     Size

013c1708 790fe704       56     

013c178c 790fe704       56     

013c235c 790fe704       56     

013c2474 790fe704       56     

013c258c 790fe704       56     

013c26a4 790fe704       56     

013c27bc 790fe704       56     

013c28d4 790fe704       56     

013c29ec 790fe704       56     

013c2b04 790fe704       56     

013c2c1c 790fe704       56     

013c2d34 790fe704       56     

013c2e54 790fe704       56     

013c2f74 790fe704       56     

013c3094 790fe704       56     

013c31b4 790fe704       56     

013c32d4 790fe704       56     

013c33f4 790fe704       56     

013c3514 790fe704       56     

013c3634 790fe704       56     

013c3754 790fe704       56     

013c3874 790fe704       56     

013c3994 790fe704       56     

013c3ab4 790fe704       56     

013c3bd4 790fe704       56     

013c3cf4 790fe704       56     

013c3e14 790fe704       56     

013c3f34 790fe704       56     

013f8084 790fe704       56     

013f81a4 790fe704       56     

013f82c4 790fe704       56     

total 31 objects

Statistics:

      MT    Count    TotalSize Class Name

790fe704       31         1736 System.Threading.Thread

Total 31 objects

Thread与ThreadPool的内存之战_3c_34

 

5,随便拿一个线程的Address来看看到底是谁占着我们的Thread而不让我们的GC回收掉




Thread与ThreadPool的内存之战_microsoft_35

0:003> !GCRoot 013c3bd4

Note: Roots found on stacks may be false positives. Run "!help gcroot" for

more info.

Scan Thread 0 OSTHread 25e4

Scan Thread 2 OSTHread 24b8

DOMAIN(00514818):HANDLE(WeakSh):241298:Root:013c3bd4(System.Threading.Thread)

Thread与ThreadPool的内存之战_i++_36


结果另我们很失望,他自己就是根,并没被其他任何对象所引用,什么情况下会出现此情况呢?我们先来看看对象在内存中分布的几种方式,我们只需在windbg里执行如下命令则知:




Thread与ThreadPool的内存之战_5e_37

0:003> !Help gcroot

-------------------------------------------------------------------------------

!GCRoot [-nostacks] <Object address>

!GCRoot looks for references (or roots) to an object. These can exist in four

places:

   1. On the stack

   2. Within a GC Handle

   3. In an object ready for finalization

   4. As a member of an object found in 1, 2 or 3 above.

First, all stacks will be searched for roots, then handle tables, and finally

the freachable queue of the finalizer. Some caution about the stack roots: 

!GCRoot doesn't attempt to determine if a stack root it encountered is valid 

or is old (discarded) data. You would have to use !CLRStack and !U to 

disassemble the frame that the local or argument value belongs to in order to 

determine if it is still in use.

Because people often want to restrict the search to gc handles and freachable

objects, there is a -nostacks option.

Thread与ThreadPool的内存之战_3c_38


 


windbg已经很清楚的告诉我们,

一个对象可以

1,在栈上

2,在一个GCHandle里(可以执行!GCHandles命令查看)

3,在FinalizeQueue里

4,是一个对象的成员

难道对象就必定在以上的“四行”之中吗?答案是不一定,还有个Gchandleleaks,就是你在内存里看不到这个Handle,它已经leak。(这种也算在GCHandle里吧)。


回头我们接着说他自己没被其他任何对象所引用,自己就是个根,但是GC却不搭理它,为何?那就是他在GCHandle里,


 




Thread与ThreadPool的内存之战_i++_39

0:003> !GCHandles

GC Handle Statistics:

Strong Handles: 14

Pinned Handles: 4

Async Pinned Handles: 0

Ref Count Handles: 0

Weak Long Handles: 0

Weak Short Handles: 31

Other Handles: 0

Statistics:

      MT    Count    TotalSize Class Name

790fd0f0        1           12 System.Object

790fcc48        1           24 System.Reflection.Assembly

790feba4        1           28 System.SharedStatics

790fe17c        1           72 System.ExecutionEngineException

790fe0e0        1           72 System.StackOverflowException

790fe044        1           72 System.OutOfMemoryException

790fed00        1          100 System.AppDomain

79100a18        4          144 System.Security.PermissionSet

790fe284        2          144 System.Threading.ThreadAbortException

790fe704       32         1792 System.Threading.Thread

7912d8f8        4         8736 System.Object[]

Total 49 objects

Thread与ThreadPool的内存之战_i++_40


而且在FinalizeQueue里也有它的踪影:


 




Thread与ThreadPool的内存之战_i++_41

0:003> !FinalizeQueue

SyncBlocks to be cleaned up: 0

MTA Interfaces to be released: 0

STA Interfaces to be released: 0

----------------------------------

generation 0 has 35 finalizable objects (00526658->005266e4)

generation 1 has 0 finalizable objects (00526658->00526658)

generation 2 has 0 finalizable objects (00526658->00526658)

Ready for finalization 0 objects (005266e4->005266e4)

Statistics:

      MT    Count    TotalSize Class Name

791037c0        1           20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle

79103764        1           20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle

79101444        2           40 Microsoft.Win32.SafeHandles.SafeFileHandle

790fe704       31         1736 System.Threading.Thread

Total 35 objects

Thread与ThreadPool的内存之战_i++_42


 


下面就来解释下什么才可以在FinalizeQueue里出现呢?答案就是有身份的人,很有身份的人,享受特殊待遇的哦!

啥身份,就是自身实现拉析构函数。


啥待遇,就是GC两次才有可能把他们部分清理掉!为啥部分,是我们不知道windows到底何时去把所有的清理掉(赖皮阿)

具体原理大家可以看.net框架去,我这里不多说。


 


说到此,也就找到我们当初30个彪形大汉为啥赖着不走的原因拉,是在0代的第一次GC时候,他们被放进FinalizeQueue,等着第二次GC他们部分才会从内存堆上消亡。
为证明我们的观点,我们可以修改程序为 :




Thread与ThreadPool的内存之战_5e_43

static void Main(string[] args)

        {

            for (int i = 0; i < 30; i++)

            {

                Thread t = new Thread(new ThreadStart(ThreadProc));

                t.Name = "Overred_" + i;

                t.Start();

            }

            GC.Collect();

            GC.Collect();

            Console.Read();

        }

Thread与ThreadPool的内存之战_microsoft_44

首先声明一点就是当我们调用一次GC.Collect();时,并不是执行一次垃圾收集,只是告诉系统我要强制进行垃圾收集,系统听到这个命令后乖不乖那就不一定拉。

当我们用Reflector查看mscorlib对Thread实现的使用也会发现他实现拉析构:



Thread与ThreadPool的内存之战_.net_45

    ~Thread()

    {

        this.InternalFinalize();

    }

Thread与ThreadPool的内存之战_3c_46


来个虎头蛇尾吧,当我们把小白鼠程序使用ThreadPool修改为:




Thread与ThreadPool的内存之战_i++_47

 static void Main(string[] args)

        {

            for (int i = 0; i < 30; i++)

            {

                ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));

            }

            Console.Read();

        }

        static  void ThreadProc(object o)

        {

            try

            {

                for (int i = 0; i < 10; i++)

                {

                     Console.WriteLine(" Value:{0}",i);

                }

               

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex.Message);

            }

        }

Thread与ThreadPool的内存之战_3c_48


 


再用windbg查看线程时则为:



Thread与ThreadPool的内存之战_3c_49

0:006> !Threads

*** ERROR: Symbol file could not be found.  Defaulted to export symbols 

for C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll - 

PDB symbol for mscorwks.dll not loaded

ThreadCount: 4

UnstartedThread: 0

BackgroundThread: 3

PendingThread: 0

DeadThread: 0

Thread与ThreadPool的内存之战_microsoft_50


而FinalizeQueue则为:



Thread与ThreadPool的内存之战_.net_51

0:006> !FinalizeQueue

SyncBlocks to be cleaned up: 0

MTA Interfaces to be released: 0

STA Interfaces to be released: 0

----------------------------------

generation 0 has 7 finalizable objects (00266658->00266674)

generation 1 has 0 finalizable objects (00266658->00266658)

generation 2 has 0 finalizable objects (00266658->00266658)

Ready for finalization 0 objects (00266674->00266674)

Statistics:

      MT    Count    TotalSize Class Name

791037c0        1           20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle

79103764        1           20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle

79101444        2           40 Microsoft.Win32.SafeHandles.SafeFileHandle

790fe704        3          168 System.Threading.Thread

Total 7 objects

Thread与ThreadPool的内存之战_3c_52


那现在又出现问题拉,既然ThreadPool这么好,那我们为啥还使用Thread呢?这个问题就是ThreadPool有个GetMaxThreads,可以通过GetMaxThreads(out int workerThreads, out int completionPortThreads);方法获取到,如果线程池满拉,则会死锁更严重!

另:ThreadPool都为后台线程。

究竟使用那个,根据情况而定,理解拉内在的东西,一切表象就简单拉。

OK,到此吧。。。

希望本文能对你有所帮助,谢谢!