日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

.Net中堆栈和堆的区别

發(fā)布時間:2023/12/10 asp.net 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 .Net中堆栈和堆的区别 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

首先堆棧和堆(托管堆)都在進程的虛擬內(nèi)存中。(在32位處理器上每個進程的虛擬內(nèi)存為4GB)

堆棧stack

1、堆棧中存儲值類型

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

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

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

5、堆棧的性能非常高,但是對于所有的變量來說還不靈活,而且變量的生命周期必須嵌套。

6、通常我們希望使用一種方法分配內(nèi)存來存儲數(shù)據(jù),并且方法退出后很長一段時間內(nèi)數(shù)據(jù)仍然可以使用。此時我們就用到了堆(托管堆)

?

堆(托管堆)heap

堆(托管堆)存儲引用類型

此堆非彼堆,.net中的堆有垃圾收集器自動管理

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

比如創(chuàng)建一個對象:

customer cus;

cus=new customer();

申明一個customer的引用cus,在堆棧上給這個引用分配存儲空間。這僅僅只是一個引用,不是實際的customer對象!

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

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

.net運行庫在堆中搜索第一個從未使用的,32字節(jié)的連續(xù)塊存儲customer對象的實例!

然后把分配給customer對象實例的地址給cus變量!

從這個例子中可以看出,建立對象引用的過程比建立值變量的過程復雜,且不能避免性能的降低!

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

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

當一個應用變量出作用于域時,它會從堆棧中刪除,但引用對象的數(shù)據(jù)仍熱保留在堆中,一直到程序結束或者該數(shù)據(jù)不被任何變量應用時,垃圾收集器會刪除它。

——————————————————————————————————————————

裝箱轉換

using System;

class Boxing

{

public? static? void Main()

{

? int i=0;

? object obj=i;

?? i=220;

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

??? obj=330;

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

}

}

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

所以運行結果是:

i=220,obj=110

i=220,obj=330

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

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

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

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

??? 4、堆區(qū):即自由存儲區(qū)

值類型區(qū)分兩種不同的內(nèi)存區(qū)域:線程堆棧(Thread Stack)和托管堆(Manged Heap)。

每個正在運行的程序都對應著一個進程(process),在一個進程內(nèi)部,可以有一個或者多個線程(thread),

每個線程都擁有一塊“自留地”,稱為“線程堆棧”,大小為1M,用于保存自身的一些數(shù)據(jù),比如函數(shù)中自定義的局部變量、函數(shù)調(diào)用時傳送的參數(shù)值等,這部分內(nèi)存域與回收不需要程序員干涉。

所有值類型的變量都是在線程堆棧中分配的。

另一塊內(nèi)存區(qū)域稱為“堆(heap)”,在.Net這種托管環(huán)境下,堆由CLR進行管理,所以又稱為“托管堆(manged heap)”。

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

在程序中我們可以隨意的使用new關鍵字創(chuàng)建多個對象,因此,托管堆中的內(nèi)存資源是可以動態(tài)申請并使用的,當然用完了必須歸還。

打個比方更容易理解:托管堆相當于一個旅館,其中房間相當于托管堆中所擁有的內(nèi)存單元。當程序員用new方法去創(chuàng)建對象時,相當于游客向旅館預訂房間,旅館管理員會先看一下有沒有合適的房間,有的話,就可以將此房間提供給游客住宿,要辦理退房手續(xù),房間又可以為其他游客提供服務了。

引用類型共有四種:類類型、接口類型、數(shù)組類型和委托類型。

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

嚴格地說,我們常說的“對象變量”其實是類類型的引用變量。但在實際人們經(jīng)常將引用類型的變量簡稱為“對象變量”,用它來指代所有四種類型的引用變量。在不致于引起混淆的情況下,我們也這么認為

在理解了對象內(nèi)存模型之后,對象變量之間的相互賦值的含義也就清楚了。請看一下代碼:

class A

{

?? public int i;

}

class Program

{

?? A a;

?? a=new A();

?? a.i=100;

? A b=null;

? b=a; //對象變量的相互賦值

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

}

注意第12和13句。

程序的運行結果是:

b.i=100;

事實上,兩個對象變量的相互賦值意味著賦值后兩個對象變量所占有的內(nèi)存單元其內(nèi)容是相同的。

詳細一些:

當創(chuàng)建對象以后,其首地址(假設為“1234 5678”)被放入到變量a自身的4個字節(jié)的內(nèi)存單元中。

有定義了一個對象變量b,其值最初為null(即對應的4個字節(jié)內(nèi)存單元中為“0000 0000”)

a變量的值被復制到b的內(nèi)存單元中,現(xiàn)在,b內(nèi)存單元的值也為“1234 5678”

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

如果通過b.i修改字段i的值,a.i也會同步變化,因為a.i與b.i其實代表同一對象的統(tǒng)一字段。

如上圖

由此得到一個重要結論:

對象變量的互相賦值不會導致對象自身被復制,其結果是兩個對象變量指向同一個對象。

另外,由于對象變量本身是一個局部變量,因此,對象本身是位于線程堆棧中的。

嚴格區(qū)分對象變量于對象變量所引用的對象,是面向對象編程的關鍵技術之一。

由于對象變量類似于一個對象指針,這就產(chǎn)生了“判斷兩個對象變量是否引用用一個對象”的問題

c#使用“==”運算符對比兩個對象變量是否引用同一個對象,“!=”比對兩個對象變量是否引用不同的對象。

A a1=new A();

A a2=new A();

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

a2=a1;//a1和a2引用相同的對象

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

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

int i=0;

int j=100;

if(i==j)

{

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

}

理解值類型與引用類型的區(qū)別在面向對象編程中非常關鍵。

1、類型,對象,堆棧和托管堆

c#的類型和對象在應用計算機內(nèi)存時,大體用到了兩種內(nèi)存,一個叫堆棧,另一個叫托管堆,下面我們用直接長方形表示堆棧,用圓角長方形來代表托管堆。

?先舉個例子,有如下兩個方法,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

{

? int sum=n1+n2;//5

? return sum;//6

}

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

上圖基本對應程序中的每個步驟。在執(zhí)行Method_1的時候,先把value1壓入堆棧頂部,然后是value2,接著下面的調(diào)用方法Add,因為方法有兩個參數(shù)是n1和n2,所以把n1和n2分別壓入堆棧,因為此處調(diào)用了一個方法,并且方法有返回值,所以這里需要保存Add的返回地址,然后進入Add方法內(nèi)部,在Add內(nèi)部,首先給sum賦值,所以把sum壓入棧頂,然后用return返回,此時,返回的返回地址就起到了作用,return會根據(jù)地址返回回去,在返回的過程中,把sum推出棧頂,找到了返回地址,但在Method_1方法中,我們希望把Add的返回值賦給Value3,此時的返回地址也被推出堆棧,把value2壓入堆棧。

雖然這個例子的結果沒大用途,但這個例子很好的說明方法被執(zhí)行時,變量在進出堆棧的情況。這里也能看出為什么方法內(nèi)部局部變量用過之后,不能在其他方法中訪問的原因。

下面我們討論一下類和對象在托管堆和堆棧中的情況。

先看一下代碼:

class Car

{

? public void Run()

?? {

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

?? }

?? public? virtual? double GetPrice()

? {

??? ?return 0;

? }

? public static void Purpose()

? {

??? console.WtriteLine("載入");

? }

}

class BMW:Car

{

? public override double GetPrice()

? {

??? return? 80000;

??}

}

上面是兩個類,一個Father一個son,son繼承了Father,因為你類中有一個virtual的BuyHouse方法,所以son類可以重寫這個方法。

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

public void Method_A()

{

? double? CarPrice;//1

? Car? car=new BMW();//2

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

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

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

}

這個方法也比較簡單,就是定義一個變量用來獲得價格,同時定義了一個父類的變量,用子類來實例化它

接下來,我們來分步驟說明:

看一下運行時堆棧和托管堆的情況:

這里需要說明的是,類是位于托管堆中的,這個類有分為四個類部,用來關聯(lián)對象;同步索引,引用完成同步(比如線程同步)需要建立的,靜態(tài)成員是屬于類的,所以在類中出現(xiàn),還有一個方法列表(這里的方法列表項與具體的方法對應)。

當Mehod_A方法的第一執(zhí)行時:

這時的CarPrice是沒有值得

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

Car? car;

car=new BWM();

先看Car

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

在看car =new BWM();

這是一個實例化過程,car變成了一個對象

這里是用子類來實例化父類類型。對象其實是子類的類型的,但變量的類型的父類的。

接下來,在Method_A中的調(diào)用的中調(diào)用car.GetPrice(),對于car來說,這個方法是虛方法(并且子類重寫了它),虛方法在調(diào)用是不會執(zhí)行類型上的方法,即不會執(zhí)行car類中的虛方法,而是執(zhí)行對象對應類的上的方法,即BWM中的GtPrice.如果Method_A中執(zhí)行方法Run()因為Run是普通實例方法,所以執(zhí)行car類中的Run方法。

如果調(diào)用了Method_A的Prupose方法,即不用變量car調(diào)用,也不用對象調(diào)用,而使類名Car調(diào)用,因為靜態(tài)方法會在類中分配內(nèi)存中。如果用Car生成多個實例,靜態(tài)成員只有一份,就是在類中,而不是在對象中。

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

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

class1 object1;

object1=new class1();

第一句定義了一個class1的引用,實質(zhì)上只是在堆棧中分配一個4個字節(jié)的空間,它將用來存儲后來實例化對象在托管堆中的地址,在windows中這需要4個字節(jié)來表示內(nèi)存地址。第二句實例化object1對象,實際上是在托管堆中開辟了一個內(nèi)存空間來存儲類class1的一個具體對象,假設這個對象需要36個字節(jié),那么object1指向的實際上是在托管推一個大小為36字節(jié)的連續(xù)內(nèi)存空間開始地址,當對象不再使用時,這個被存儲在堆棧中引用變量將被刪除,但是從上述機制中可以看出,在托管堆中這個引用指向的對象仍然存在,其空間何時被釋放取決垃圾收集器而不是引用變量失去作用域時。

在使用電腦的過程中大家可以都有過這種經(jīng)驗,電腦用久了以后程序會變得越來越慢,其中一個重要的原因就就系統(tǒng)存在中大量內(nèi)存碎片,就是因為程序反復在堆棧中創(chuàng)建和釋放變量,久而久之可用變量在內(nèi)存中將不再是連續(xù)的內(nèi)存空間,為了尋址這些變量也會增加系統(tǒng)開銷。在.net中這種類型將得到很大改善,這是因為有了垃圾回收集器的工作,垃圾收集器將會壓縮托管堆的內(nèi)存空間,保證可用變量在一個連續(xù)的內(nèi)存空間中,同時將堆棧中引用變量中的地址改為新的地址,這將會帶來額外的系統(tǒng)開銷,但是,其帶來的好處會抵消這種影響,而另外的一個好處是,程序員將不再花上大量的心思在內(nèi)存泄露問題上。

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

另一方面,在某些時候c#程序需要追求速度,比如對一個含有大量成員的數(shù)組的操作,如仍使用傳統(tǒng)的類來操作,將不會得到很好的性能,因為數(shù)組在c#中實際是System.Array的實例,會存儲在托管堆中,這將會對運算造成大量的額外的操作,因為除了垃圾收集器除了會壓縮托管堆、更新引用地址,還會維護托管堆的信息列表。所幸的是c#中同樣能夠通過不安全代碼使用c++程序員通常喜歡的方式來編碼,在標記為unsafe的代碼塊使用指針,這和在c++中使用指針沒有什么不同,變量也是存在堆棧中,在這種情況下聲明一個數(shù)組可以使用stacklloc語法,比如聲明一個存儲有50個double類型的數(shù)組:

double*? pDouble=stackalloc double[50]

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

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

棧(stack)是操作系統(tǒng)在建立某個進程時或者線程(在支持多線程的操作系統(tǒng)中是線程)為這個線程的存儲區(qū)域,該區(qū)域具有FIFO的特性,在編譯的時候可以指定需要的Stack的大小。在編程中,例如c/c++中,所有的局部變量都是從棧中分配內(nèi)存空間,實際上也不是什么都分配,只是從棧頂向上用就行,在推出函數(shù)的時候,只修改棧指針就可以把棧中的內(nèi)容銷毀,所以速度最快。

堆(Heap)是應用程序在運行的時候請求操作系統(tǒng)給自己內(nèi)存,一般是申請/給予的過程,c/c++分別用malloc/New請求分配Heap,用free/delete銷毀內(nèi)存。由于從操作系統(tǒng)管理的內(nèi)存分配所以在分配和銷毀時都要占用時間,所以堆的效率要低的多!,但是堆的好處是可以做的很大,c/c++堆分配的Heap是不初始化的。

在Java中除了簡單類型(int,char等)都是在堆中分配內(nèi)存,這也是程序慢的一個主要原因。但是跟C/C++不同,Java中分配Heap內(nèi)存是自動初始化的。在Java中所有的對象(包括int的wrapper?? Integer)都是在堆中分配的,但是這個對象的引用卻是在Stack中分配。也就是說在建立一個對象時從兩個地方都分配內(nèi)存,在Heap中分配的內(nèi)存實際建立這個對象,而在Stack中分配的內(nèi)存只是一個指向這個堆對象的指針(引用)而已。

在.NET的所有技術中,最具爭議的恐怕是垃圾收集(Garbage Collection,GC)了。作為.NET框架中一個重要的部分,托管堆和垃圾收集機制對我們中的大部分人來說是陌生的概念。在這篇文章中將要討論托管堆,和你將從中得到怎樣的好處。

轉載于:https://www.cnblogs.com/leo7718/p/3757975.html

總結

以上是生活随笔為你收集整理的.Net中堆栈和堆的区别的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。