這篇的由來其實很奇妙,前幾天在FB有人在討論某個資深工程師不懂method前面加上static的差異,下面一大串的炮火覺得也太不專業了。
直到前幾天群組有個朋友提出
『其實我真的不太敢確切的說static到底是怎麼運作的,他牽涉到記憶體行為,在執行階段有沒有static差在哪裡,記憶體是共用還是指向同一個部』
於是又拉了一長串的討論出來,討論出結果後催生了這篇文章
先談談static戰到最後的結果
static method、method在記憶體之中只存在於一份,這點無庸置疑,並且不存在於.NET的記憶體管理範圍內,也就是說method不存在stack以及heap之中。
那存在哪裡呢?答案是CLR,想知道細節的可以參考這篇
原文是這樣
「Methods are stored somewhere else in the memory. Notice that methods are per-class, not per-instance. So typically, the number of methods doesn't change over the run-time of a program (there are exceptions). In traditional models, the place where the methods live is called the "code segment". In .net, it's more difficult: the methods originally live in the assembly, and get mapped into the process memory. There, the just-in-time compiler creates a second copy of some methods in native code; this copy gets executed. The JIT code may get created and deleted several times during the runtime, so it is practical to view it also as living "in Heap".」
那寫static好嗎?
不需要進行測試以及抽換的代碼,是可以接受的,例如一些共用的運算,像是固定格式化文字等可以使用,但是在撰寫商業邏輯的程式碼時,不建議使用!
原因有幾點
1.static不利於測試,有碰到DB、第三方等外部服務時,無法做假資料進行測試
2.static的方法不利於抽換,程式碼內部需要變更時,不能直接更改實體,而是所有呼叫的method都要重改,寫一千次就改一千次,嚇不嚇人?
3.多數開發者在寫static method時候,會忽略以物件的方式思考,造成許多職責耦合在一起,資深工程師可能會注意,但初階工程師很容易萬劫不復,這會是開發思考上的問題
可是static method效能不是比較好?
static method不是效能好,而是少了建立實體,new的過程,但這種效能是可以忽略不計的。
舉個例子來說,MVC的POST所綁定的Model,傳送到View的ViewModel,都是在建立實體,既然整個程式都在大量使用了,在意這個微不足道的比例時在是有點難過。相較之下,開發人員更需要的是理解記憶體回收機制。
另外在method中,編譯完之後,在呼叫method前,會隱含加上this關鍵字,以利使用物件中的method
說了這麼多終於進入正題Garbage Collection
想瞭解的可以參考這個影片,在打折時候可以購買,大約300台幣左右。
Garbage Collection是.NET在運行時,自行管理記憶體中,資料存放位置、生命週期的一套機制。
不懂的可以參考這篇圖解,算是講得滿詳細的
以下會一條一條從大方向開始講到細節,一步一步的建立觀念,讀懂了再往下會比較好
一、型別存放在stack、heap的時機
1.記憶體存放區塊分為二個區塊,一個是stack,一個是heap
2.stack存放實質型別,如int、bool等,這部分的記憶體不受垃圾回收機制影響
3.heap存放參考型別,如class、string,垃圾回收機制就是這裡主管
4.heap裡雖存放參考型別,但參考型別內若是有包含實質型別,則這個實質型別也存放於heap。
舉例來說,man的類別裡面有個age的int屬性,因為man是class,所以是參考型別,參考型別內的age為int,是實質型別。
運作的方式為,在heap的區塊中,劃分出man的記憶體位置,包含了一個age的格子,而這個格子指向heap之中的age,也就是說會再劃分一個age的記憶體區塊存放於heap之中
這段很重要要搞清楚,總之就是參考型別之中的實質型別,不存在於stack,而是heap
二、GC的生命週期
1.標記階段,查找並創建所有活動對象的列表。啟動時會將所有執行緒暫停
2.重新定位階段,更新對將被壓縮的對象的引用。開始清除未使用、未參考的資料,並壓縮資料
3.清除完畢後,記憶體之中的資料,會變得不連貫
4.將剩餘的資料重新排序,讓記憶體之中的資料變得連貫,將增加讀取速度(不了解就當成是磁碟重組吧XD)
5.資料搬移到記憶體的末端,Gen0的會搬移到Gen1,Gen1的會搬移到Gen2
註※大型的物件並不會被壓縮,只要大於85,000字節都算是大型物件
Gen0:所有臨時的變數都存放這,直到Gen0的區塊滿了會啟動Gen0的GC
Gen1:Gen0以及Gen1的緩衝區,直到Gen1的區塊滿了會啟動Gen0、1的GC
Gen2:所有長壽命的資料都存在這,程式碼之中的靜態資料也存在這,若是Gen2的區塊滿了,則會啟動Gen0、1、2的GC,所以也被稱為完整的垃圾回收
有幾點比較值得提到
1.假設List已經有10筆資料,存放於G0,將第2/4筆資料清除後,啟動G0的GC,則第2/4筆資料會被GC回收,而該List則搬移到G1,倘若此時第2筆資料再重新指向值,則第2筆的資料,會存活在G0之中,而List仍然存在於G1
2.Gen2啟動時,會再將字節大於85000的物件,存放進large object heap之中
3.一般物件若是存活到Gen2,需要進行二次的搬移。若是大型物件活到Gen2啟動後仍存活,則會進行三次的搬移
綜合以上三點,優化垃圾回收管理機制可以推導出
1.小型物件的生命週期較短,多數存在於Gen0
2.大型物件的生命週期較長,多數會存活到Gen2
綜合以上的概念,會有一些可以針對這套機制優化的方向,改日再寫一篇新的文章來做分享摟
歡迎您的加入,讓這個社群更加美好!