• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            posts - 195,  comments - 30,  trackbacks - 0

            原文地址:http://blog.csdn.net/baoxuetianxia/archive/2008/11/04/3218913.aspx
            首先堆棧和堆(托管堆)都在進(jìn)程的虛擬內(nèi)存中。(在32位處理器上每個(gè)進(jìn)程的虛擬內(nèi)存為4GB)

            堆棧stack

            堆棧中存儲(chǔ)值類(lèi)型。

            堆棧實(shí)際上是向下填充,即由高內(nèi)存地址指向低內(nèi)存地址填充。

            堆棧的工作方式是先分配內(nèi)存的變量后釋放(先進(jìn)后出原則)。

            堆棧中的變量是從下向上釋放,這樣就保證了堆棧中先進(jìn)后出的規(guī)則不與變量的生命周期起沖突!

            堆棧的性能非常高,但是對(duì)于所有的變量來(lái)說(shuō)還不太靈活,而且變量的生命周期必須嵌套。

            通常我們希望使用一種方法分配內(nèi)存來(lái)存儲(chǔ)數(shù)據(jù),并且方法退出后很長(zhǎng)一段時(shí)間內(nèi)數(shù)據(jù)仍然可以使用。此時(shí)就要用到堆(托管堆)!

             

             堆(托管堆)heap

            堆(托管堆)存儲(chǔ)引用類(lèi)型。

            此堆非彼堆,.NET中的堆由垃圾收集器自動(dòng)管理。

            與堆棧不同,堆是從下往上分配,所以自由的空間都在已用空間的上面。

            比如創(chuàng)建一個(gè)對(duì)象:

            Customer cus;

            cus = new Customer();

            申明一個(gè)Customer的引用cus,在堆棧上給這個(gè)引用分配存儲(chǔ)空間。這僅僅只是一個(gè)引用,不是實(shí)際的Customer對(duì)象!

            cus占4個(gè)字節(jié)的空間,包含了存儲(chǔ)Customer的引用地址。

            接著分配上的內(nèi)存以存儲(chǔ)Customer對(duì)象的實(shí)例,假定Customer對(duì)象的實(shí)例是32字節(jié),為了在堆上找到一個(gè)存儲(chǔ)Customer對(duì)象的存儲(chǔ)位置。

            .NET運(yùn)行庫(kù)在堆中搜索第一個(gè)從未使用的,32字節(jié)的連續(xù)塊存儲(chǔ)Customer對(duì)象的實(shí)例!

            然后把分配給Customer對(duì)象實(shí)例的地址賦給cus變量!

             

            從這個(gè)例子中可以看出,建立對(duì)象引用的過(guò)程比建立值變量的過(guò)程復(fù)雜,且不能避免性能的降低!

            實(shí)際上就是.NET運(yùn)行庫(kù)保存對(duì)的狀態(tài)信息,在堆中添加新數(shù)據(jù)時(shí),堆棧中的引用變量也要更新。性能上損失很多!

            有種機(jī)制在分配變量?jī)?nèi)存的時(shí)候,不會(huì)受到堆棧的限制:把一個(gè)引用變量的值賦給一個(gè)相同類(lèi)型的變量,那么這兩個(gè)變量就引用同一個(gè)堆中的對(duì)象。

            當(dāng)一個(gè)應(yīng)用變量出作用域時(shí),它會(huì)從堆棧中刪除。但引用對(duì)象的數(shù)據(jù)仍然保留在堆中,一直到程序結(jié)束 或者 該數(shù)據(jù)不被任何變量應(yīng)用時(shí),垃圾收集器會(huì)刪除它。

                                                                                                                                                                                                    

            裝箱轉(zhuǎn)化

            using System;

            class Boxing

            {

              public static void Main()

              { int i=110;

                object obj=i;

                i=220;

                Console.WriteLine("i={0},obj={1}",i,obj);

                obj=330;

                Console.WriteLine("i={0},obj={1}",i,obj);

             

              }

            }

            定義整數(shù)類(lèi)型變量I的時(shí)候,這個(gè)變量占用的內(nèi)存是內(nèi)存棧中分配的,第二句是裝箱操作將變量 110存放到了內(nèi)存堆中,而定義object對(duì)象類(lèi)型的變量obj則在內(nèi)存棧中,并指向int類(lèi)型的數(shù)值110,而該數(shù)值是付給變量i的數(shù)值副本。

            所以運(yùn)行結(jié)果是

            i=220,obj=110

            i=220,obj=330

            內(nèi)存格局通常分為四個(gè)區(qū)

              全局?jǐn)?shù)據(jù)區(qū):存放全局變量,靜態(tài)數(shù)據(jù),常量

              代碼區(qū):存放所有的程序代碼

              棧區(qū):存放為運(yùn)行而分配的局部變量,參數(shù),返回?cái)?shù)據(jù),返回地址等,

              堆區(qū):即自由存儲(chǔ)區(qū)

            值類(lèi)型變量與引用類(lèi)型變量的內(nèi)存分配模型也不一樣。為了理解清楚這個(gè)問(wèn)題,讀者首

            先必須區(qū)分兩種不同類(lèi)型的內(nèi)存區(qū)域:線(xiàn)程堆棧(Thread Stack)和托管堆(Managed Heap)。

            每個(gè)正在運(yùn)行的程序都對(duì)應(yīng)著一個(gè)進(jìn)程(process),在一個(gè)進(jìn)程內(nèi)部,可以有一個(gè)或多

            個(gè)線(xiàn)程(thread),每個(gè)線(xiàn)程都擁有一塊“自留地”,稱(chēng)為“線(xiàn)程堆棧”,大小為1M,用于保

            存自身的一些數(shù)據(jù),比如函數(shù)中定義的局部變量、函數(shù)調(diào)用時(shí)傳送的參數(shù)值等,這部分內(nèi)存

            區(qū)域的分配與回收不需要程序員干涉。

            所有值類(lèi)型的變量都是在線(xiàn)程堆棧中分配的。

            另一塊內(nèi)存區(qū)域稱(chēng)為“堆(heap)”,在.NET 這種托管環(huán)境下,堆由CLR 進(jìn)行管理,所

            以又稱(chēng)為“托管堆(managed heap)”。

            用new 關(guān)鍵字創(chuàng)建的類(lèi)的對(duì)象時(shí),分配給對(duì)象的內(nèi)存單元就位于托管堆中。

            在程序中我們可以隨意地使用new 關(guān)鍵字創(chuàng)建多個(gè)對(duì)象,因此,托管堆中的內(nèi)存資源

            是可以動(dòng)態(tài)申請(qǐng)并使用的,當(dāng)然用完了必須歸還。

            打個(gè)比方更易理解:托管堆相當(dāng)于一個(gè)旅館,其中的房間相當(dāng)于托管堆中所擁有的內(nèi)存

            單元。當(dāng)程序員用new 方法創(chuàng)建對(duì)象時(shí),相當(dāng)于游客向旅館預(yù)訂房間,旅館管理員會(huì)先看

            一下有沒(méi)有合適的空房間,有的話(huà),就可以將此房間提供給游客住宿。當(dāng)游客旅途結(jié)束,要

            辦理退房手續(xù),房間又可以為其他旅客提供服務(wù)了。

            從表 1 可以看到,引用類(lèi)型共有四種:類(lèi)類(lèi)型、接口類(lèi)型、數(shù)組類(lèi)型和委托類(lèi)型。

            所有引用類(lèi)型變量所引用的對(duì)象,其內(nèi)存都是在托管堆中分配的。

            嚴(yán)格地說(shuō),我們常說(shuō)的“對(duì)象變量”其實(shí)是類(lèi)類(lèi)型的引用變量。但在實(shí)際中人們經(jīng)常將

            引用類(lèi)型的變量簡(jiǎn)稱(chēng)為“對(duì)象變量”,用它來(lái)指代所有四種類(lèi)型的引用變量。在不致于引起

            混淆的情況下,本書(shū)也采用了這種慣例。

            在了解了對(duì)象內(nèi)存模型之后,對(duì)象變量之間的相互賦值的含義也就清楚了。請(qǐng)看以下代

            碼(示例項(xiàng)目ReferenceVariableForCS):

            class A

            02 {

            03 public int i;

            04 }

            05 class Program

            06 {

            07 static void Main(string[] args)

            08 {

            09 A a ;

            10 a= new A();

            11 a.i = 100;

            12 A b=null;

            13 b = a; //對(duì)象變量的相互賦值

            14 Console.WriteLine("b.i=" + b.i); //b.i=?

            15 }

            16 }

            注意第12 和13 句。

            程序的運(yùn)行結(jié)果是:

            b.i=100;

            請(qǐng)讀者思索一下:兩個(gè)對(duì)象變量的相互賦值意味著什么?

            事實(shí)上,兩個(gè)對(duì)象變量的相互賦值意味著賦值后兩個(gè)對(duì)象變量所占用的內(nèi)存單元其內(nèi)容

            是相同的。

            講得詳細(xì)一些:

            第10 句創(chuàng)建對(duì)象以后,其首地址(假設(shè)為“1234 5678”)被放入到變量a 自身的4 個(gè)

            字節(jié)的內(nèi)存單元中。

            第12 句又定義了一個(gè)對(duì)象變量b,其值最初為null(即對(duì)應(yīng)的4 個(gè)字節(jié)內(nèi)存單元中為

            “0000 0000”)。

            第13 句執(zhí)行以后,a 變量的值被復(fù)制到b 的內(nèi)存單元中,現(xiàn)在,b 內(nèi)存單元中的值也為

            “1234 5678”。

            根據(jù)前面介紹的對(duì)象內(nèi)存模型,我們知道現(xiàn)在變量a 和b 都指向同一個(gè)實(shí)例對(duì)象。

            如果通過(guò)b.i 修改字段i 的值,a.i 也會(huì)同步變化,因?yàn)閍.i 與b.i 其實(shí)代表同一對(duì)象的同

            一字段。

            整個(gè)過(guò)程可以用圖 9 來(lái)說(shuō)明:

             

            圖 9 對(duì)象變量的相互賦值

            由此得到一個(gè)重要結(jié)論:

            對(duì)象變量的相互賦值不會(huì)導(dǎo)致對(duì)象自身被復(fù)制,其結(jié)果是兩個(gè)對(duì)象變量指向同一對(duì)象。

            另外,由于對(duì)象變量本身是一個(gè)局部變量,因此,對(duì)象變量本身是位于線(xiàn)程堆棧中的。

            嚴(yán)格區(qū)分對(duì)象變量與對(duì)象變量所引用的對(duì)象,是面向?qū)ο缶幊痰年P(guān)鍵之一。

            由于對(duì)象變量類(lèi)似于一個(gè)對(duì)象指針,這就產(chǎn)生了“判斷兩個(gè)對(duì)象變量是否引用同一對(duì)象”

            的問(wèn)題。

            C#使用“==”運(yùn)算符比對(duì)兩個(gè)對(duì)象變量是否引用同一對(duì)象,“!=”比對(duì)兩個(gè)對(duì)象變量

            22

            是否引用不同的對(duì)象。參看以下代碼:

            //a1與a2引用不同的對(duì)象

            A a1= new A();

            A a2= new A();

            Console.WriteLine(a1 == a2);//輸出:false

            a2 = a1;//a1與a2引用相同的對(duì)象

            Console.WriteLine(a1 == a2);//輸出:true

            需要注意的是,如果“==”被用在值類(lèi)型的變量之間,則比對(duì)的是變量的內(nèi)容:

            int i = 0;

            int j = 100;

            if (i == j)

            {

            Console.WriteLine("i與j的值相等");

            }

            理解值類(lèi)型與引用類(lèi)型的區(qū)別在面向?qū)ο缶幊讨蟹浅jP(guān)鍵。

             

             

             

            1、類(lèi)型,對(duì)象,堆棧和托管堆

            C#的類(lèi)型和對(duì)象在應(yīng)用計(jì)算機(jī)內(nèi)存時(shí),大體用到兩種內(nèi)存,一個(gè)

            叫堆棧,另一個(gè)叫托管堆,下面我們用直角長(zhǎng)方形來(lái)代表堆棧,

            用圓角長(zhǎng)方形來(lái)代表托管堆。

             

            首先討論一下方法內(nèi)部變量的存放。

            先舉個(gè)例子,有如下兩個(gè)方法,Method_1 和Add,分別如下:

            public void Method_1()

            {

            int value1=10; //1

            int value2=20; //2

            int value3=Add(value,value); //3

            }

            public int Add(int n1,int n2)//4

            {

            rnt sum=n1+n2;//5

            return sum;//6

            }

            這段代碼的執(zhí)行,用圖表示為:

             

             

             

             

             

            上述的每個(gè)圖片,基本對(duì)應(yīng)程序中的每個(gè)步驟。在開(kāi)始執(zhí)行Met

            hod_1的時(shí)候,先把value1 壓入堆棧頂,然后是value2,接

            下來(lái)的是調(diào)用方法Add,因?yàn)榉椒ㄓ袃蓚€(gè)參數(shù)是n1 和n2,所以

            把n1 和n2 分別壓入堆棧,因?yàn)榇颂幨钦{(diào)用了一個(gè)方法,并且方

            法有返回值,所以這里需要保存Add的返回地址,然后進(jìn)入Ad

            d方法內(nèi)部,在Add內(nèi)部,首先是給sum 賦值,所以把sum 壓

            入棧項(xiàng),然后用return 返回,此時(shí),先前的返回地址就起到了

            作用,return 會(huì)根據(jù)地址返回去的,在返回的過(guò)程中,把sum

            推出棧頂,找到了返回地址,但在Method_1 方法中,我們希望

            把Add的返回值賦給value3,此時(shí)的返回地址也被推出堆棧,

            把value3 壓入堆棧。雖這個(gè)例子的結(jié)果在這里沒(méi)有多大用途,

            但這個(gè)例子很好的說(shuō)明了在方法被執(zhí)行時(shí),變量與進(jìn)出堆棧的情

            況。這里也能看出為什么方法內(nèi)部的局變量用過(guò)后,不能在其他

            方法中訪問(wèn)的原因。

            其次來(lái)討論一下類(lèi)和對(duì)象在托管堆和堆棧中的情況。

            先看一下代碼:

            class Car

            {

            public void Run()

            {

            Console.WriteLine("一切正常");

            }

            public virtual double GetPrice()

            {

            return 0;

            }

            public static void Purpose()

            {

            Console.WriteLine("載人");

            }

            PDF 文件使用 "pdfFactory Pro" 試用版本創(chuàng)建 fw w w . f i n e p rint.cn

            }

            class BMW : Car

            {

            public override double GetPrice()

            {

            return 800000;

            }

            }

            上面是兩個(gè)類(lèi),一個(gè)Father一個(gè)Son,Son 繼承了Father,

            因?yàn)槟泐?lèi)中有一個(gè)virtual的BuyHouse 方法,所以Son類(lèi)可以重

            寫(xiě)這個(gè)方法。

            下面接著看調(diào)用代碼。

            public void Method_A()

            {

            double CarPrice;//1

            Car car = new BMW();//2

            CarPrice = car.GetPrice();//調(diào)用虛方法(其實(shí)調(diào)用的是重寫(xiě)后

            的方法)

            car.Run();//調(diào)用實(shí)例化方法

            Car.Purpose();//調(diào)用靜態(tài)方法

            }

            這個(gè)方法也比較簡(jiǎn)單,就是定義一個(gè)變量用來(lái)獲得價(jià)格,同時(shí)

            定義了一個(gè)父類(lèi)的變量,用子類(lèi)來(lái)實(shí)例化它。

            接下來(lái),我們分步來(lái)說(shuō)明。

            看一下運(yùn)行時(shí)堆棧和托管堆的情部我

             

             

             


            這里需要說(shuō)明的是,類(lèi)是位于托管堆中的,每個(gè)類(lèi)又分為四個(gè)

            類(lèi)部,類(lèi)指針,用來(lái)關(guān)聯(lián)對(duì)象;同步索引,用來(lái)完成同步(比如線(xiàn)

            程的同步)需建立的;靜態(tài)成員是屬于類(lèi)的,所以在類(lèi)中出現(xiàn),還

            有一個(gè)方法列表(這里的方法列表項(xiàng)與具體的方法對(duì)應(yīng))。

            當(dāng)Method_A方法的第一步執(zhí)行時(shí):

             

             


            這時(shí)的CarPrice 是沒(méi)有值的

            當(dāng)Method_A方法執(zhí)行到第二步,其實(shí)第二步又可以分成

            Car car;

            car = new BMW();

            先看Car car;

             

             

            car在這里是一個(gè)方法內(nèi)部的變量,所以被壓到堆棧中。

            再看 car = new BMW();

            這是一個(gè)實(shí)例化過(guò)程,car變成了一個(gè)對(duì)象

             

             


            這里是用子類(lèi)來(lái)實(shí)例化父類(lèi)型。對(duì)象其實(shí)是子類(lèi)的類(lèi)型的,但

            變量的類(lèi)型是父類(lèi)的。

            接下來(lái),在Method_A中的調(diào)用的中調(diào)用car.GetPrice(),

            對(duì)于Car來(lái)說(shuō),這個(gè)方法是虛方法(并且子類(lèi)重寫(xiě)了它),虛方

            法在調(diào)用是不會(huì)執(zhí)行類(lèi)型上的方法,即不會(huì)執(zhí)行Car類(lèi)中的虛方

            法,而是執(zhí)行對(duì)象對(duì)應(yīng)類(lèi)上的方法,即BMW中的GtPrice。

            如果Method_A中執(zhí)行方法Run(),因?yàn)镽un是普通實(shí)例方

            法,所以會(huì)執(zhí)行Car類(lèi)中的Run 方法。

            如果調(diào)用了Method_A的Purpose 方法,即不用變量car調(diào)

            用,也不用對(duì)象調(diào)用,而是用類(lèi)名Car調(diào)用,因?yàn)殪o態(tài)方法會(huì)在

            類(lèi)中分配內(nèi)存的。如果用Car生成多個(gè)實(shí)例,靜態(tài)成員只有一份,

            就是在類(lèi)中,而不是在對(duì)象中。

            33333333333333333333333333333333

            在32位的Windows操作系統(tǒng)中,每個(gè)進(jìn)程都可以使用4GB的內(nèi)存,這得益于虛擬尋址技術(shù),在這4GB的內(nèi)存中存儲(chǔ)著可執(zhí)行代碼、代碼加載的DLL和程序運(yùn)行的所有變量,在C#中,虛擬內(nèi)存中有個(gè)兩個(gè)存儲(chǔ)變量的區(qū)域,一個(gè)稱(chēng)為堆棧,一個(gè)稱(chēng)為托管堆,托管堆的出現(xiàn)是.net不同于其他語(yǔ)言的地方,堆棧存儲(chǔ)值類(lèi)型數(shù)據(jù),而托管堆存儲(chǔ)引用類(lèi)型如類(lèi)、對(duì)象,并受垃圾收集器的控制和管理。在堆棧中,一旦變量超出使用范圍,其使用的內(nèi)存空間會(huì)被其他變量重新使用,這時(shí)其空間中存儲(chǔ)的值將被其他變量覆蓋而不復(fù)存在,但有時(shí)候我們希望這些值仍然存在,這就需要托管堆來(lái)實(shí)現(xiàn)。我們用幾段代碼來(lái)說(shuō)明其工作原理,假設(shè)已經(jīng)定義了一個(gè)類(lèi)class1:

            class1 object1;

            object1=new class1();

            第一句定義了一個(gè)class1的引用,實(shí)質(zhì)上只是在堆棧中分配了一個(gè)4個(gè)字節(jié)的空間,它將用來(lái)存府后來(lái)實(shí)例化對(duì)象在托管堆中的地址,在windows中這需要4個(gè)字節(jié)來(lái)表示內(nèi)存地址。第二句實(shí)例化object1對(duì)象,實(shí)際上是在托管堆中開(kāi)僻了一個(gè)內(nèi)存空間來(lái)存儲(chǔ)類(lèi)class1的一個(gè)具體對(duì)象,假設(shè)這個(gè)對(duì)象需要36個(gè)字節(jié),那么object1指向的實(shí)際上是在托管堆一個(gè)大小為36個(gè)字節(jié)的連續(xù)內(nèi)存空間開(kāi)始的地址。由此也可以看出在C#編譯器中為什么不允許使用未實(shí)例化的對(duì)象,因?yàn)檫@個(gè)對(duì)象在托管堆中還不存在。當(dāng)對(duì)象不再使用時(shí),這個(gè)被存儲(chǔ)在堆棧中的引用變量將被刪除,但是從上述機(jī)制可以看出,在托管堆中這個(gè)引用指向的對(duì)象仍然存在,其空間何時(shí)被釋放取決垃圾收集器而不是引用變量失去作用域時(shí)。

            在使用電腦的過(guò)程中大家可能都有過(guò)這種經(jīng)驗(yàn):電腦用久了以后程序運(yùn)行會(huì)變得越來(lái)越慢,其中一個(gè)重要原因就是系統(tǒng)中存在大量?jī)?nèi)存碎片,就是因?yàn)槌绦蚍磸?fù)在堆棧中創(chuàng)建和釋入變量,久而久之可用變量在內(nèi)存中將不再是連續(xù)的內(nèi)存空間,為了尋址這些變量也會(huì)增加系統(tǒng)開(kāi)銷(xiāo)。在.net中這種情形將得到很大改善,這是因?yàn)橛辛死占鞯墓ぷ鳎占鲗?huì)壓縮托管堆的內(nèi)存空間,保證可用變量在一個(gè)連續(xù)的內(nèi)存空間內(nèi),同時(shí)將堆棧中引用變量中的地址改為新的地址,這將會(huì)帶來(lái)額外的系統(tǒng)開(kāi)銷(xiāo),但是,其帶來(lái)的好處將會(huì)抵消這種影響,而另外一個(gè)好處是,程序員將不再花上大量的心思在內(nèi)在泄露問(wèn)題上。

            當(dāng)然,以C#程序中不僅僅只有引用類(lèi)型的變量,仍然也存在值類(lèi)型和其他托管堆不能管理的對(duì)象,如果文件名柄、網(wǎng)絡(luò)連接和數(shù)據(jù)庫(kù)連接,這些變量的釋放仍需要程序員通過(guò)析構(gòu)函數(shù)或IDispose接口來(lái)做。

            另一方面,在某些時(shí)候C#程序也需要追求速度,比如對(duì)一個(gè)含用大量成員的數(shù)組的操作,如果仍使用傳統(tǒng)的類(lèi)來(lái)操作,將不會(huì)得到很好的性能,因?yàn)閿?shù)組在C#中實(shí)際是System.Array的實(shí)例,會(huì)存儲(chǔ)在托管堆中,這將會(huì)對(duì)運(yùn)算造成大量的額外的操作,因?yàn)槌死占鞒藭?huì)壓縮托管堆、更新引用地址、還會(huì)維護(hù)托管堆的信息列表。所幸的是C#中同樣能夠通過(guò)不安全代碼使用C++程序員通常喜歡的方式來(lái)編碼,在標(biāo)記為unsafe的代碼塊使用指針,這和在C++中使用指針沒(méi)有什么不同,變量也是存府在堆棧中,在這種情況下聲明一個(gè)數(shù)組可以使用stackalloc語(yǔ)法,比如聲明一個(gè)存儲(chǔ)有50個(gè)double類(lèi)型的數(shù)組:

            double* pDouble=stackalloc double[50]

            stackalloc會(huì)給pDouble數(shù)組在堆棧中分配50個(gè)double類(lèi)型大小的內(nèi)存空間,可以使用pDouble[0]、*(pDouble+1)這種方式操作數(shù)組,與在C++中一樣,使用指針時(shí)必須知道自己在做什么,確保訪問(wèn)的正確的內(nèi)存空間,否則將會(huì)出現(xiàn)無(wú)法預(yù)料的錯(cuò)誤。

            掌握托管堆、堆棧、垃圾收集器和不安全代碼的工作原理和方式,將有助于你成為真正的優(yōu)秀C#程序員。

            進(jìn)程中每個(gè)線(xiàn)程都有自己的堆棧,這是一段線(xiàn)程創(chuàng)建時(shí)保留下的地址區(qū)域。我們的“棧內(nèi)存”即在此。至于“堆”內(nèi)存,我個(gè)人認(rèn)為在未用new定義時(shí),堆應(yīng)該就是未“保留”未“提交”的自由空間,new的功能是在這些自由空間中保留(并提交?)出一個(gè)地址范圍

            棧(Stack)是操作系統(tǒng)在建立某個(gè)進(jìn)程時(shí)或者線(xiàn)程(在支持多線(xiàn)程的操作系統(tǒng)中是線(xiàn)程)為這個(gè)線(xiàn)程建立的存儲(chǔ)區(qū)域,該區(qū)域具有FIFO的特性,在編譯的時(shí)候可以指定需要的Stack的大小。在編程中,例如C/C++中,所有的局部變量都是從棧中分配內(nèi)存空間,實(shí)際上也不是什么分配,只是從棧頂向上用就行,在退出函數(shù)的時(shí)候,只是修改棧指針就可以把棧中的內(nèi)容銷(xiāo)毀,所以速度最快。  
              堆(Heap)是應(yīng)用程序在運(yùn)行的時(shí)候請(qǐng)求操作系統(tǒng)分配給自己內(nèi)存,一般是申請(qǐng)/給予的過(guò)程,C/C++分別用malloc/New請(qǐng)求分配Heap,用free/delete銷(xiāo)毀內(nèi)存。由于從操作系統(tǒng)管理的內(nèi)存分配所以在分配和銷(xiāo)毀時(shí)都要占用時(shí)間,所以用堆的效率低的多!但是堆的好處是可以做的很大,C/C++對(duì)分配的Heap是不初始化的。  
              在Java中除了簡(jiǎn)單類(lèi)型(int,char等)都是在堆中分配內(nèi)存,這也是程序慢的一個(gè)主要原因。但是跟C/C++不同,Java中分配Heap內(nèi)存是自動(dòng)初始化的。在Java中所有的對(duì)象(包括int的wrapper   Integer)都是在堆中分配的,但是這個(gè)對(duì)象的引用卻是在Stack中分配。也就是說(shuō)在建立一個(gè)對(duì)象時(shí)從兩個(gè)地方都分配內(nèi)存,在Heap中分配的內(nèi)存實(shí)際建立這個(gè)對(duì)象,而在Stack中分配的內(nèi)存只是一個(gè)指向這個(gè)堆對(duì)象的指針(引用)而已。

            在.NET的所有技術(shù)中,最具爭(zhēng)議的恐怕是垃圾收集(Garbage Collection,GC)了。作為.NET框架中一個(gè)重要的部分,托管堆和垃圾收集機(jī)制對(duì)我們中的大部分人來(lái)說(shuō)是陌生的概念。在這篇文章中將要討論托管堆,和你將從中得到怎樣的好處。
              為什么要托管堆?
              .NET框架包含一個(gè)托管堆,所有的.NET語(yǔ)言在分配引用類(lèi)型對(duì)象時(shí)都要使用它。像值類(lèi)型這樣的輕量級(jí)對(duì)象始終分配在棧中,但是所有的類(lèi)實(shí)例和數(shù)組都被生成在一個(gè)內(nèi)存池中,這個(gè)內(nèi)存池就是托管堆。
              垃圾收集器的基本算法很簡(jiǎn)單:
              ● 將所有的托管內(nèi)存標(biāo)記為垃圾
              ● 尋找正被使用的內(nèi)存塊,并將他們標(biāo)記為有效
              ● 釋放所有沒(méi)有被使用的內(nèi)存塊
              ● 整理堆以減少碎片
              托管堆優(yōu)化
              看上去似乎很簡(jiǎn)單,但是垃圾收集器實(shí)際采用的步驟和堆管理系統(tǒng)的其他部分并非微不足道,其中常常涉及為提高性能而作的優(yōu)化設(shè)計(jì)。舉例來(lái)說(shuō),垃圾收集遍歷整個(gè)內(nèi)存池具有很高的開(kāi)銷(xiāo)。然而,研究表明大部分在托管堆上分配的對(duì)象只有很短的生存期,因此堆被分成三個(gè)段,稱(chēng)作generations。新分配的對(duì)象被放在generation 0中。這個(gè)generation是最先被回收的——在這個(gè)generation中最有可能找到不再使用的內(nèi)存,由于它的尺寸很小(小到足以放進(jìn)處理器的L2 cache中),因此在它里面的回收將是最快和最高效的。
              托管堆的另外一種優(yōu)化操作與locality of reference規(guī)則有關(guān)。該規(guī)則表明,一起分配的對(duì)象經(jīng)常被一起使用。如果對(duì)象們?cè)诙阎形恢煤芫o湊的話(huà),高速緩存的性能將會(huì)得到提高。由于托管堆的天性,對(duì)象們總是被分配在連續(xù)的地址上,托管堆總是保持緊湊,結(jié)果使得對(duì)象們始終彼此靠近,永遠(yuǎn)不會(huì)分得很遠(yuǎn)。這一點(diǎn)與標(biāo)準(zhǔn)堆提供的非托管代碼形成了鮮明的對(duì)比,在標(biāo)準(zhǔn)堆中,堆很容易變成碎片,而且一起分配的對(duì)象經(jīng)常分得很遠(yuǎn)。
              還有一種優(yōu)化是與大對(duì)象有關(guān)的。通常,大對(duì)象具有很長(zhǎng)的生存期。當(dāng)一個(gè)大對(duì)象在.NET托管堆中產(chǎn)生時(shí),它被分配在堆的一個(gè)特殊部分中,這部分堆永遠(yuǎn)不會(huì)被整理。因?yàn)橐苿?dòng)大對(duì)象所帶來(lái)的開(kāi)銷(xiāo)超過(guò)了整理這部分堆所能提高的性能。
              關(guān)于外部資源(External Resources)的問(wèn)題
              垃圾收集器能夠有效地管理從托管堆中釋放的資源,但是資源回收操作只有在內(nèi)存緊張而觸發(fā)一個(gè)回收動(dòng)作時(shí)才執(zhí)行。那么,類(lèi)是怎樣來(lái)管理像數(shù)據(jù)庫(kù)連接或者窗口句柄這樣有限的資源的呢?等待,直到垃圾回收被觸發(fā)之后再清理數(shù)據(jù)庫(kù)連接或者文件句柄并不是一個(gè)好方法,這會(huì)嚴(yán)重降低系統(tǒng)的性能。
              所有擁有外部資源的類(lèi),在這些資源已經(jīng)不再用到的時(shí)候,都應(yīng)當(dāng)執(zhí)行Close或者Dispose方法。從Beta2(譯注:本文中所有的Beta2均是指.NET Framework Beta2,不再特別注明)開(kāi)始,Dispose模式通過(guò)IDisposable接口來(lái)實(shí)現(xiàn)。這將在本文的后續(xù)部分討論。
              需要清理外部資源的類(lèi)還應(yīng)當(dāng)實(shí)現(xiàn)一個(gè)終止操作(finalizer)。在C#中,創(chuàng)建終止操作的首選方式是在析構(gòu)函數(shù)中實(shí)現(xiàn),而在Framework層,終止操作的實(shí)現(xiàn)則是通過(guò)重載System.Object.Finalize 方法。以下兩種實(shí)現(xiàn)終止操作的方法是等效的:
              ~OverdueBookLocator()
              {
               Dispose(false);
              }
              和:
              public void Finalize()
              {
               base.Finalize();
               Dispose(false);
              }
              在C#中,同時(shí)在Finalize方法和析構(gòu)函數(shù)實(shí)現(xiàn)終止操作將會(huì)導(dǎo)致錯(cuò)誤的產(chǎn)生。
              除非你有足夠的理由,否則你不應(yīng)該創(chuàng)建析構(gòu)函數(shù)或者Finalize方法。終止操作會(huì)降低系統(tǒng)的性能,并且增加執(zhí)行期的內(nèi)存開(kāi)銷(xiāo)。同時(shí),由于終止操作被執(zhí)行的方式,你并不能保證何時(shí)一個(gè)終止操作會(huì)被執(zhí)行。
              內(nèi)存分配和垃圾回收的細(xì)節(jié)
              對(duì)GC有了一個(gè)總體印象之后,讓我們來(lái)討論關(guān)于托管堆中的分配與回收工作的細(xì)節(jié)。托管堆看起來(lái)與我們已經(jīng)熟悉的C++編程中的傳統(tǒng)的堆一點(diǎn)都不像。在傳統(tǒng)的堆中,數(shù)據(jù)結(jié)構(gòu)習(xí)慣于使用大塊的空閑內(nèi)存。在其中查找特定大小的內(nèi)存塊是一件很耗時(shí)的工作,尤其是當(dāng)內(nèi)存中充滿(mǎn)碎片的時(shí)候。與此不同,在托管堆中,內(nèi)存被組制成連續(xù)的數(shù)組,指針總是巡著已經(jīng)被使用的內(nèi)存和未被使用的內(nèi)存之間的邊界移動(dòng)。當(dāng)內(nèi)存被分配的時(shí)候,指針只是簡(jiǎn)單地遞增——由此而來(lái)的一個(gè)好處是,分配操作的效率得到了很大的提升。
              當(dāng)對(duì)象被分配的時(shí)候,它們一開(kāi)始被放在generation 0中。當(dāng)generation 0的大小快要達(dá)到它的上限的時(shí)候,一個(gè)只在generation 0中執(zhí)行的回收操作被觸發(fā)。由于generation 0的大小很小,因此這將是一個(gè)非常快的GC過(guò)程。這個(gè)GC過(guò)程的結(jié)果是將generation 0徹底的刷新了一遍。不再使用的對(duì)象被釋放,確實(shí)正被使用的對(duì)象被整理并移入generation 1中。
              當(dāng)generation 1的大小隨著從generation 0中移入的對(duì)象數(shù)量的增加而接近它的上限的時(shí)候,一個(gè)回收動(dòng)作被觸發(fā)來(lái)在generation 0和generation 1中執(zhí)行GC過(guò)程。如同在generation 0中一樣,不再使用的對(duì)象被釋放,正在被使用的對(duì)象被整理并移入下一個(gè)generation中。大部分GC過(guò)程的主要目標(biāo)是generation 0,因?yàn)樵趃eneration 0中最有可能存在大量的已不再使用的臨時(shí)對(duì)象。對(duì)generation 2的回收過(guò)程具有很高的開(kāi)銷(xiāo),并且此過(guò)程只有在generation 0和generation 1的GC過(guò)程不能釋放足夠的內(nèi)存時(shí)才會(huì)被觸發(fā)。如果對(duì)generation 2的GC過(guò)程仍然不能釋放足夠的內(nèi)存,那么系統(tǒng)就會(huì)拋出OutOfMemoryException異常
              帶有終止操作的對(duì)象的垃圾收集過(guò)程要稍微復(fù)雜一些。當(dāng)一個(gè)帶有終止操作的對(duì)象被標(biāo)記為垃圾時(shí),它并不會(huì)被立即釋放。相反,它會(huì)被放置在一個(gè)終止隊(duì)列(finalization queue)中,此隊(duì)列為這個(gè)對(duì)象建立一個(gè)引用,來(lái)避免這個(gè)對(duì)象被回收。后臺(tái)線(xiàn)程為隊(duì)列中的每個(gè)對(duì)象執(zhí)行它們各自的終止操作,并且將已經(jīng)執(zhí)行過(guò)終止操作的對(duì)象從終止隊(duì)列中刪除。只有那些已經(jīng)執(zhí)行過(guò)終止操作的對(duì)象才會(huì)在下一次垃圾回收過(guò)程中被從內(nèi)存中刪除。這樣做的一個(gè)后果是,等待被終止的對(duì)象有可能在它被清除之前,被移入更高一級(jí)的generation中,從而增加它被清除的延遲時(shí)間。
              需要執(zhí)行終止操作的對(duì)象應(yīng)當(dāng)實(shí)現(xiàn)IDisposable接口,以便客戶(hù)程序通過(guò)此接口快速執(zhí)行終止動(dòng)作。IDisposable接口包含一個(gè)方法——Dispose。這個(gè)被Beta2引入的接口,采用一種在Beta2之前就已經(jīng)被廣泛使用的模式實(shí)現(xiàn)。從本質(zhì)上講,一個(gè)需要終止操作的對(duì)象暴露出Dispose方法。這個(gè)方法被用來(lái)釋放外部資源并抑制終止操作,就象下面這個(gè)程序片斷所演示的那樣:
              public class OverdueBookLocator: IDisposable
              {
               ~OverdueBookLocator()
               {
               InternalDispose(false);
               }
               public void Dispose()
               {
               InternalDispose(true);
               }
               protected void InternalDispose(bool disposing)
               {
               if(disposing)
               {
               GC.SuppressFinalize(this);
               // Dispose of managed objects if disposing.
               }
               // free external resources here
               }
              }
            這些都是.NET中CLR的概念,和C#沒(méi)多大關(guān)系。
            使用基于CLR的語(yǔ)言編譯器開(kāi)發(fā)的代碼稱(chēng)為托管代碼。
            托管堆是CLR中自動(dòng)內(nèi)存管理的基礎(chǔ)。初始化新進(jìn)程時(shí),運(yùn)行時(shí)會(huì)為進(jìn)程保留一個(gè)連續(xù)的地址空間區(qū)域。這個(gè)保留的地址空間被稱(chēng)為托管堆。托管堆維護(hù)著一個(gè)指針,用它指向?qū)⒃诙阎蟹峙涞南乱粋€(gè)對(duì)象的地址。最初,該指針設(shè)置為指向托管堆的基址。
            認(rèn)真看MSDN Library,就會(huì)搞清楚這些概念。


            以下代碼說(shuō)明的很形象:

            //引用類(lèi)型('class' 類(lèi)類(lèi)型)
            class SomeRef { public int32 x;}
             
            //值類(lèi)型('struct')
            struct SomeVal(pulic Int32 x;}

            static void ValueTypeDemo()
            {
              SomeRef r1=new SomeRef();//分配在托管堆
              SomeVal v1=new SomeVal();//堆棧上
              r1.x=5;//解析指針
              v1.x=5;//在堆棧上修改

              SomeRef r2=r1;//僅拷貝引用(指針)
              SomeVal v2=v1;//先在堆棧上分配,然后拷貝成員

              r1.x=8;//改變了r1,r2的值
              v1.x=9;//改變了v1,沒(méi)有改變v2
            }

            4444444444444444444444444444444444444444444444444444444

            棧是內(nèi)存中完全用于存儲(chǔ)局部變量或成員字段(值類(lèi)型數(shù)據(jù))的高效的區(qū)域,但其大小有限制。

            托管堆所占內(nèi)存比棧大得多,當(dāng)訪問(wèn)速度較慢。托管堆只用于分配內(nèi)存,一般由CLR(Common Language Runtime)來(lái)處理內(nèi)存釋放問(wèn)題。

            當(dāng)創(chuàng)建值類(lèi)型數(shù)據(jù)時(shí),在棧上分配內(nèi)存;

            當(dāng)創(chuàng)建引用型數(shù)據(jù)時(shí),在托管堆上分配內(nèi)存并返回對(duì)象的引用。注意這個(gè)對(duì)象的引用,像其他局部變量一樣也是保存在棧中的。該引用指向的值則位于托管堆中。

            如果創(chuàng)建了一個(gè)包含值類(lèi)型的引用類(lèi)型,比如數(shù)組,其元素的值也是存放在托管堆中而非棧中的。當(dāng)從數(shù)組中檢索數(shù)據(jù)時(shí),獲得本地使用的元素值的副本,而該副本這時(shí)候就是存放在棧中的了。所以,不能籠統(tǒng)的說(shuō)“值類(lèi)型保存在棧中,引用類(lèi)型保存在托管堆中”。

            值類(lèi)型和引用類(lèi)型的區(qū)別:引用類(lèi)型存儲(chǔ)在托管堆的唯一位置中,其存在于托管堆中某個(gè)地方,由使用該實(shí)體的變量引用;而值類(lèi)型存儲(chǔ)在使用它們的地方,有幾處在使用,就有幾個(gè)副本存在。

            對(duì)于引用類(lèi)型,如果在聲明變量的時(shí)候沒(méi)有使用new運(yùn)算符,運(yùn)行時(shí)就不會(huì)給它分配托管堆上的內(nèi)存空間,而是在棧上給它分配一個(gè)包含null值的引用。對(duì)于值類(lèi)型,運(yùn)行時(shí)會(huì)給它分配棧上的空間,并調(diào)用默認(rèn)的構(gòu)造函數(shù),來(lái)初始化對(duì)象的狀態(tài)。

            55555555555555555555555555555555555555555555555555

            一、棧和托管堆

                通用類(lèi)型系統(tǒng)(CTS)區(qū)分兩種基本類(lèi)型:值類(lèi)型和引用類(lèi)型。它們之間的根本區(qū)別在于它們?cè)趦?nèi)存中的存儲(chǔ)方式。.NET使用兩種不同的物理內(nèi)存塊來(lái)存儲(chǔ)數(shù)據(jù)—棧和托管堆。如下圖所示:

             

             

            二  類(lèi)型層次結(jié)構(gòu)

               CTS定義了一種類(lèi)型層次結(jié)構(gòu),該結(jié)構(gòu)不僅僅描述了不同的預(yù)定義類(lèi)型,還指出了用戶(hù)定義類(lèi)型在層次結(jié)構(gòu)種的

             

             

            posted on 2011-04-08 15:00 luis 閱讀(6569) 評(píng)論(0)  編輯 收藏 引用

            只有注冊(cè)用戶(hù)登錄后才能發(fā)表評(píng)論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問(wèn)   Chat2DB   管理


            <2011年4月>
            272829303112
            3456789
            10111213141516
            17181920212223
            24252627282930
            1234567

            常用鏈接

            留言簿(3)

            隨筆分類(lèi)

            隨筆檔案

            文章分類(lèi)

            文章檔案

            友情鏈接

            搜索

            •  

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            亚洲国产精品人久久| 亚洲性久久久影院| 91精品久久久久久无码| 亚洲国产精品久久久久网站| 久久综合一区二区无码| 久久久久久精品免费免费自慰| 无码人妻久久一区二区三区免费 | 人妻精品久久久久中文字幕69| 久久精品www人人爽人人| 久久久久国产精品| 精品久久久久久久无码| 久久无码一区二区三区少妇| 欧美成a人片免费看久久| 久久综合给合综合久久| 久久久久波多野结衣高潮| 国产精品久久久久AV福利动漫| 热99re久久国超精品首页| 超级97碰碰碰碰久久久久最新| 国产综合久久久久| 亚洲精品tv久久久久| 国产精品美女久久久久av爽| 精品久久久久久无码专区不卡| 欧美无乱码久久久免费午夜一区二区三区中文字幕 | 久久成人小视频| 久久国产精品视频| 好久久免费视频高清| 久久这里只有精品18| 狠狠色丁香久久婷婷综合图片| 久久久久久久久久免免费精品| 66精品综合久久久久久久| 国内精品久久久久久野外| 99精品国产在热久久| 中文字幕无码精品亚洲资源网久久| 久久精品国产亚洲AV不卡| 国产精品18久久久久久vr| 亚洲国产精品无码久久98| 成人久久免费网站| 日产精品久久久久久久| 国产情侣久久久久aⅴ免费| 久久av无码专区亚洲av桃花岛| 亚洲中文字幕无码久久2020|