天天看點

spring事務管理器設計思想(一)

首先堆棧和堆(托管堆)都在程序的虛拟記憶體中。(在32位處理器上每個程序的虛拟記憶體為4GB)

堆棧stack

1、堆棧中存儲值類型

2、堆棧實際上是向下填充,即由高記憶體位址指向低記憶體位址填充

3、堆棧的工作方式是先配置設定記憶體的變量後釋放(先進後出原則)

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

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

6、通常我們希望使用一種方法配置設定記憶體來存儲資料,并且方法退出後很長一段時間内資料仍然可以使用。此時我們就用到了堆(托管堆)

堆(托管堆)heap

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

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

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

比如建立一個對象:

customer cus;

cus=new customer();

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

cus占4個位元組的空間,包含了存儲customer的引用位址。

接着配置設定堆上的記憶體以存儲customer對象的執行個體,假定customer對象的執行個體是32位元組,為了在堆上找到一個存儲customer對象的存儲位置。

.net運作庫在堆中搜尋第一個從未使用的,32位元組的連續塊存儲customer對象的執行個體!

然後把配置設定給customer對象執行個體的位址給cus變量!

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

實際上就是.net運作庫儲存對狀态資訊,在堆中添加新資料時,堆棧中引用變量也要更新。性能上損失很多!

有種機制在配置設定變量記憶體的時候,不會受到堆棧的限制:把一個引用變量的值賦給一個相同類型的變量,那麼這兩個變量就引用一個堆中的對象。

當一個應用變量出作用于域時,它會從堆棧中删除,但引用對象的資料仍熱保留在堆中,一直到程式結束或者該資料不被任何變量應用時,垃圾收集器會删除它。

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

裝箱轉換

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;

}

定義整形類型變量i的時候,這個變量占用的記憶體是記憶體棧中配置設定的,第二句是裝箱操作将變量110存放到了記憶體堆中,而定義object對象類型的變量obj則在記憶體棧中,并指向int類型的數值為110,而該數值是付給變量i的數值副本。

是以運作結果是:

i=220,obj=110

i=220,obj=330

spring事務管理器設計思想(一)

 記憶體格局通常分為四個區

    1、全局資料區:存放全局變量,靜态資料,常量

    2、代碼區:存放所有的程式代碼

    3、棧區:存放為運作而配置設定的局部變量,參數、傳回資料,傳回位址等,

    4、堆區:即自由存儲區

值類型區分兩種不同的記憶體區域:線程堆棧(Thread Stack)和托管堆(Manged Heap)。

每個正在運作的程式都對應着一個程序(process),在一個程序内部,可以有一個或者多個線程(thread),

每個線程都擁有一塊“自留地”,稱為“線程堆棧”,大小為1M,用于儲存自身的一些資料,比如函數中自定義的局部變量、函數調用時傳送的參數值等,這部分記憶體域與回收不需要程式員幹涉。

所有值類型的變量都是線上程堆棧中配置設定的。

另一塊記憶體區域稱為“堆(heap)”,在.Net這種托管環境下,堆由CLR進行管理,是以又稱為“托管堆(manged heap)”。

用new 關鍵字建立的類的對象時,配置設定給對象的記憶體單元就位于托管堆中。

在程式中我們可以随意的使用new關鍵字建立多個對象,是以,托管堆中的記憶體資源是可以動态申請并使用的,當然用完了必須歸還。

打個比方更容易了解:托管堆相當于一個旅館,其中房間相當于托管堆中所擁有的記憶體單元。當程式員用new方法去建立對象時,相當于遊客向旅館預訂房間,旅館管理者會先看一下有沒有合适的房間,有的話,就可以将此房間提供給遊客住宿,要辦理退房手續,房間又可以為其他遊客提供服務了。

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

所有引用類型變量所引用的對象,其記憶體都是在托管堆中配置設定的。

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

在了解了對象記憶體模型之後,對象變量之間的互相指派的含義也就清楚了。請看一下代碼:

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;

事實上,兩個對象變量的互相指派意味着指派後兩個對象變量所占有的記憶體單元其内容是相同的。

詳細一些:

當建立對象以後,其首位址(假設為“1234 5678”)被放入到變量a自身的4個位元組的記憶體單元中。

有定義了一個對象變量b,其值最初為null(即對應的4個位元組記憶體單元中為“0000 0000”)

a變量的值被複制到b的記憶體單元中,現在,b記憶體單元的值也為“1234 5678”

根據上面介紹的對象記憶體模型,我們知道現在變量a和b都指向同一個執行個體對象。

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

spring事務管理器設計思想(一)

如上圖

由此得到一個重要結論:

對象變量的互相指派不會導緻對象自身被複制,其結果是兩個對象變量指向同一個對象。

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

嚴格區分對象變量于對象變量所引用的對象,是面向對象程式設計的關鍵技術之一。

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

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

A a1=new A();

A a2=new A();

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

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

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

需要注意的是,如果“==”被用到值類型的變量之間,則比對的變量的内容:

int i=0;

int j=100;

if(i==j)

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

了解值類型與引用類型的差別在面向對象程式設計中非常關鍵。

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

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

spring事務管理器設計思想(一)

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

這段代碼執行,用圖表示為為:

spring事務管理器設計思想(一)

上圖基本對應程式中的每個步驟。在執行Method_1的時候,先把value1壓入堆棧頂部,然後是value2,接着下面的調用方法Add,因為方法有兩個參數是n1和n2,是以把n1和n2分别壓入堆棧,因為此處調用了一個方法,并且方法有傳回值,是以這裡需要儲存Add的傳回位址,然後進入Add方法内部,在Add内部,首先給sum指派,是以把sum壓入棧頂,然後用return傳回,此時,傳回的傳回位址就起到了作用,return會根據位址傳回回去,在傳回的過程中,把sum推出棧頂,找到了傳回位址,但在Method_1方法中,我們希望把Add的傳回值賦給Value3,此時的傳回位址也被推出堆棧,把value2壓入堆棧。

雖然這個例子的結果沒大用途,但這個例子很好的說明方法被執行時,變量在進出堆棧的情況。這裡也能看出為什麼方法内部局部變量用過之後,不能在其他方法中通路的原因。

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

先看一下代碼:

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類可以重寫這個方法。

下面接着看調用代碼。

public void Method_A()

  double  CarPrice;//1

  Car  car=new BMW();//2

  CarPrice=car.GetPrice();//調用虛方法(其實調用的是重寫後的方法)

  car.Run();//調用執行個體化方法

  car.Purpose();//調用靜态方法

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

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

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

spring事務管理器設計思想(一)

這裡需要說明的是,類是位于托管堆中的,這個類有分為四個類部,用來關聯對象;同步索引,引用完成同步(比如線程同步)需要建立的,靜态成員是屬于類的,是以在類中出現,還有一個方法清單(這裡的方法清單項與具體的方法對應)。

當Mehod_A方法的第一執行時:

spring事務管理器設計思想(一)

這時的CarPrice是沒有值得

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

Car  car;

car=new BWM();

先看Car

spring事務管理器設計思想(一)

car在這裡是一個方法内部的變量,是以被壓力到堆棧中。

在看car =new BWM();

這是一個執行個體化過程,car變成了一個對象

spring事務管理器設計思想(一)

這裡是用子類來執行個體化父類類型。對象其實是子類的類型的,但變量的類型的父類的。

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

如果調用了Method_A的Prupose方法,即不用變量car調用,也不用對象調用,而使類名Car調用,因為靜态方法會在類中配置設定記憶體中。如果用Car生成多個執行個體,靜态成員隻有一份,就是在類中,而不是在對象中。

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

在32位的window作業系統中,每個程序都可以用4GB的記憶體,這得益于虛拟尋址技術,在這4GB的記憶體中存儲這可執行代碼、代碼加載的DLL和程式運作的所有變量,在c#中,虛拟記憶體中有個兩個存儲變量區域,一個稱為堆棧,一個稱為托管堆,托管堆的出現是.net不同于其他語言的地方,堆棧存儲值類型資料,而托管堆棧存儲引用類型如類、對象,并受垃圾回收集器的控制和管理。在堆棧中,一旦變量超出使用範圍,其使用的記憶體空間會被其他變量重新使用,這時其空間存儲的值被其他變量覆寫而不複存在,但有時候我們希望有些值仍然存在,這需要托管堆來實作,我們用幾段代碼來說明其工作原理,假設已經定義了一個類class:

class1 object1;

object1=new class1();

第一句定義了一個class1的引用,實質上隻是在堆棧中配置設定一個4個位元組的空間,它将用來存儲後來執行個體化對象在托管堆中的位址,在windows中這需要4個位元組來表示記憶體位址。第二句執行個體化object1對象,實際上是在托管堆中開辟了一個記憶體空間來存儲類class1的一個具體對象,假設這個對象需要36個位元組,那麼object1指向的實際上是在托管推一個大小為36位元組的連續記憶體空間開始位址,當對象不再使用時,這個被存儲在堆棧中引用變量将被删除,但是從上述機制中可以看出,在托管堆中這個引用指向的對象仍然存在,其空間何時被釋放取決垃圾收集器而不是引用變量失去作用域時。

在使用電腦的過程中大家可以都有過這種經驗,電腦用久了以後程式會變得越來越慢,其中一個重要的原因就就系統存在中大量記憶體碎片,就是因為程式反複在堆棧中建立和釋放變量,久而久之可用變量在記憶體中将不再是連續的記憶體空間,為了尋址這些變量也會增加系統開銷。在.net中這種類型将得到很大改善,這是因為有了垃圾回收集器的工作,垃圾收集器将會壓縮托管堆的記憶體空間,保證可用變量在一個連續的記憶體空間中,同時将堆棧中引用變量中的位址改為新的位址,這将會帶來額外的系統開銷,但是,其帶來的好處會抵消這種影響,而另外的一個好處是,程式員将不再花上大量的心思在記憶體洩露問題上。

當然,以c#程式中不僅僅隻有引用類型的變量,仍然存在值類型和其他托管堆不能管理的對象,如果檔案名柄、網絡連接配接和資料庫連接配接,這些變量的釋放仍然需要程式員通過析構函數或者IDispose接口來做。

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

double*  pDouble=stackalloc double[50]

stackalloc會給pDouble數組在堆棧中配置設定50個double類型大小的記憶體空間,可以使用pDouble[0]、*(pDouble+1)這種方式操作數組,與在c++中一樣,使用指針必須知道自己在做什麼,確定通路的正确的記憶體空間,否則會出現無法預料的錯誤。

程序中每個線程都有自己的堆棧,這是一段線程建立時保留下的位址區域。我們的“棧記憶體”即在此。至于“堆”記憶體,我個人認為在未使用new定義時,堆應該就是未“保留”為“送出”的自由空間,new的功能是在這些自由空間中保留出一個位址範圍

棧(stack)是作業系統在建立某個程序時或者線程(在支援多線程的作業系統中是線程)為這個線程的存儲區域,該區域具有FIFO的特性,在編譯的時候可以指定需要的Stack的大小。在程式設計中,例如c/c++中,所有的局部變量都是從棧中配置設定記憶體空間,實際上也不是什麼都配置設定,隻是從棧頂向上用就行,在推出函數的時候,隻修改棧指針就可以把棧中的内容銷毀,是以速度最快。

堆(Heap)是應用程式在運作的時候請求作業系統給自己記憶體,一般是申請/給予的過程,c/c++分别用malloc/New請求配置設定Heap,用free/delete銷毀記憶體。由于從作業系統管理的記憶體配置設定是以在配置設定和銷毀時都要占用時間,是以堆的效率要低的多!,但是堆的好處是可以做的很大,c/c++堆配置設定的Heap是不初始化的。

在Java中除了簡單類型(int,char等)都是在堆中配置設定記憶體,這也是程式慢的一個主要原因。但是跟C/C++不同,Java中配置設定Heap記憶體是自動初始化的。在Java中所有的對象(包括int的wrapper  

Integer)都是在堆中配置設定的,但是這個對象的引用卻是在Stack中配置設定。也就是說在建立一個對象時從兩個地方都配置設定記憶體,在Heap中配置設定的記憶體實際建立這個對象,而在Stack中配置設定的記憶體隻是一個指向這個堆對象的指針(引用)而已。

在.NET的所有技術中,最具争議的恐怕是垃圾收集(Garbage

Collection,GC)了。作為.NET架構中一個重要的部分,托管堆和垃圾收集機制對我們中的大部分人來說是陌生的概念。在這篇文章中将要讨論托管堆,和你将從中得到怎樣的好處。