Thread與ThreadPool使用的時候在內存裏對象是如何分佈的呢?
今天咱們就從內存堆的角度分析下二者。
先上小白鼠代碼:html
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);
}
}
以上代碼很是簡單,就是循環啓動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,查看該程序的線程狀況windows
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: 32
UnstartedThread: 0
BackgroundThread: 1
PendingThread: 0
DeadThread: 30
Hosted 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
看紅色加粗部分,咱們總共有32個線程,而DeadThread爲30個(其餘2個爲程序自身全部,其中一個BackgroundThread),先告訴你這30個死線程正式咱們循環建立的線程,能夠回答我提的第一個問題拉,沒錯,他們通通死拉,並且不會醒來,還佔地方(不是永遠佔地方,待會咱們用GC手動讓它們消亡)。sass
3,而後咱們繼續看看內存堆上它們這些壞傢伙如何分佈:app
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.ExecutionContext
790fe704 31 1736 System.Threading.Thread
791249e8 60 1920 System.Threading.ThreadStart
Total 155 objects
紅色部分,31個Thread,對應着31個Context,每一個線程在windows底層都是一個內核對象和一個棧空間,內核對象存放一些線程的統計信息,好比計數器以及一個上下文,就是我上次執行到那裏等。而棧空間則是用來存放線程參數等。
4,咱們來具體看下這些Thread們的MethodTable框架
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
5,隨便拿一個線程的Address來看看究竟是誰佔着咱們的Thread而不讓咱們的GC回收掉函數
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)
結果另咱們很失望,他本身就是根,並沒被其餘任何對象所引用,什麼狀況下會出現此狀況呢?咱們先來看看對象在內存中分佈的幾種方式,咱們只需在windbg裏執行以下命令則知:工具
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.
windbg已經很清楚的告訴咱們,
一個對象能夠
1,在棧上
2,在一個GCHandle裏(能夠執行!GCHandles命令查看)
3,
在FinalizeQueue裏
4,是一個對象的成員
難道對象就一定在以上的「四行」之中嗎?答案是不必定,還有個Gchandleleaks,就是你在內存裏看不到這個Handle,它已經leak。(這種也算在GCHandle裏吧)。
回頭咱們接着說他本身沒被其餘任何對象所引用,本身就是個根,可是GC卻不搭理它,爲什麼?那就是他在GCHandle裏,
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
並且在FinalizeQueue裏也有它的蹤跡:
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
下面就來解釋下什麼才能夠在FinalizeQueue裏出現呢?答案就是有身份的人,頗有身份的人,享受特殊待遇的哦!
啥身份,就是自身實現拉析構函數。
啥待遇,就是GC兩次纔有可能把他們部分清理掉!爲啥部分,是咱們不知道windows到底什麼時候去把全部的清理掉(賴皮阿)
具體原理你們能夠看.net框架去,我這裏很少說。
說到此,也就找到咱們當初30個彪形大漢爲啥賴着不走的緣由拉,是在0代的第一次GC時候,他們被放進FinalizeQueue,等着第二次GC他們部分纔會從內存堆上消亡。
爲證實咱們的觀點,咱們能夠修改程序爲 :post
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();
}
首先聲明一點就是當咱們調用一次GC.Collect();時,並非執行一次垃圾收集,只是告訴系統我要強制進行垃圾收集,系統聽到這個命令後乖不乖那就不必定拉。
當咱們用Reflector查看mscorlib對Thread實現的使用也會發現他實現拉析構:
~
Thread()
{
this
.InternalFinalize();
}
來個有始無終吧,當咱們把小白鼠程序使用ThreadPool修改成:this
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);
}
}
再用windbg查看線程時則爲:
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
而FinalizeQueue則爲:
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
那如今又出現問題拉,既然ThreadPool這麼好,那咱們爲啥還使用Thread呢?這個問題就是ThreadPool有個GetMaxThreads,能夠經過GetMaxThreads(out int workerThreads, out int completionPortThreads);方法獲取到,若是線程池滿拉,則會死鎖更嚴重!
另:ThreadPool都爲後臺線程。
究竟使用那個,根據狀況而定,理解拉內在的東西,一切表象就簡單拉。
OK,到此吧。。。
但願本文能對你有所幫助,謝謝! spa