繁忙的工做總容易讓咱們忽視最基礎的知識,手裏的活停一停,下樓呼吸下新鮮空氣(北京的朋友抱歉了),讓大腦切換下進程。閉包
回想工做中咱們所遇到的難點,嗯,好多都是咱們對基礎知識瞭解得不夠透徹,或者說只知道了表層的東西。而每每咱們老是被這些表層東西所欺騙了,最後等待咱們的就是bug量增多,性能低下,運行不穩定,維護成本劇增,客戶滿意度降低,更嚴重的即可能致使項目進入惡性循環。可想而知,咱們所面臨的挑戰是多麼艱鉅。函數
言歸正傳,本節咱們主要講的內容是.Net中變量內存分配,是的,就講這麼簡單的知識。性能
1、咱們先看下類的成員變量:優化
public class Customerthis
{spa
int customerId;3d
string customerName;對象
}blog
public class Customer進程
{
int customerId = 0;
string customerName = null;
}
乍看之下,咱們會以爲第二種寫法更加合情合理,但咱們的編譯器沒有那麼智能。他總會首先幫咱們將成員變量賦予一個初始值,當咱們人爲賦予初始值的時候,編譯器會在調用默認構造函數以前用咱們賦予的初始化值替換他默認初始化的值。也就是除非咱們初始化的數據是業務相關數據,不然咱們賦默認值則給JIT增長了負擔。
2、靜態變量:
當咱們增長個CustomerCount int類型靜態變量,並賦予初始值0時,這個時候咱們會發現,編譯器默認給咱們建立了一個靜態構造函數,並在靜態構造函數中替換掉編譯器默認初始化的值。若是咱們不人爲初始化呢,其實編譯器還會自動爲咱們提供一個靜態的構造函數,除非是咱們的類沒有靜態變量。靜態構造函數只會執行一次。
3、常量:
一、咱們知道靜態常量無需分配內存的,並且必須定義的時候就得賦初始化值,這樣的優勢咱們也不難發現他會帶來潛伏的bug,當他的引用(實際上是那個常量值)分佈到不一樣的程序集中時,若是後期因爲業務變化須要改變常量值時,項目必須總體從新編譯一次,不然其餘引用的程序集常量還保持原來值。能夠看到,這給咱們維護帶來了不便。
二、因爲靜態常量帶來的不便,因此這個時候動態常量便應運而生了,動態常量須要分配內存的,其必須在聲明或在構造函數中賦初始化值,若是不人爲賦值,編譯器會初始化默認值的,但這樣的常量是毫無心義的。
4、局部變量:
咱們都很清楚,C#中訪問局部變量的以前,咱們必須人爲爲他賦初值,若是沒有賦值,則編譯就不會經過。其實這個時候,編譯器是很聰明的,咱們聲明瞭一個局部變量,但不給他賦值,咱們要這個局部變量幹嗎呢(閉包狀況不在這個範圍)?他並非類成員變量,能夠共享。反過來想想,若是編譯器也爲局部變量賦初值,那麼JIT的負擔得多麼重。因此默認狀況下,C#是不會初始化局部變量的。
下面咱們來看看局部變量內存分配的狀況:
public class Customer
{
public Customer GetCustomer()
{
int customerId = 4;
Customer customer = GetCustomerById(customerId);
return customer;
}
private Customer GetCustomerById(int id)
{
Customer customer = new Customer();
return customer;
}
}
好,具體狀況咱們來看圖:
說明:①②給局部變量customerId賦值。
③④將this和customerId值壓入堆棧,準備調用GetCustomerById方法。
說明:⑤⑥調用GetCustomerById方法
⑦⑧在託管堆上建立Customer對象,並給局部變量customer賦值
說明:⑨⑩給返回值returnObj賦值,returnObj是編譯器建立的臨時存放返回值得局部變量
⑪將返回值的內容壓入堆棧
說明:⑫GetCustomerById方法執行完畢,將返回值傳遞到調用方維護的堆棧裏。此時GetCustomerById局部變量都被堆棧彈出,這部份內存自動收回。
⑬給局部變量customer賦值
說明:⑭⑮給局部變量returnObj賦值
⑯將返回值壓入堆棧中
上圖,只是爲了形象說明局部變量內存分配狀況,並非最終本地代碼執行時內存狀況,JIT還會爲咱們作不少優化,並且局部變量表沒有體現到堆棧中。
從上圖中,咱們能夠知道,局部變量中值類型的值存放到堆棧中(並非全部值類型都存放在堆棧區,有例外狀況,好比閉包和yield的狀況就比較特殊,編譯器會提高局部變量的),而引用類型則是在託管堆中開闢內存存放引用類型對象,其對象引用則是存放到堆棧中。
——Aaron.Pan