## **記憶體分區** 先回顧一下C#的記憶體種類 1. 棧區:由編譯器自動分配釋放 ,存放值類型的對象本身,引用類型的引用地址(指針),靜態區對象的引用地址(指針),常量區對象的引用地址(指針)等。其操作方式類似於數據結構中的棧。 2. 堆區(托管堆):用於存放引用類型對象本身。在c#中由.net平 ...
記憶體分區
先回顧一下C#的記憶體種類
-
棧區:由編譯器自動分配釋放 ,存放值類型的對象本身,引用類型的引用地址(指針),靜態區對象的引用地址(指針),常量區對象的引用地址(指針)等。其操作方式類似於數據結構中的棧。
-
堆區(托管堆):用於存放引用類型對象本身。在c#中由.net平臺的垃圾回收機制(GC)管理。棧,堆都屬於動態存儲區,可以實現動態分配。
-
靜態區及常量區:用於存放靜態類,靜態成員(靜態變數,靜態方法),常量的對象本身。由於存在棧內的引用地址都在程式運行開始最先入棧,因此靜態區和常量區內的對象的生命周期會持續到程式運行結束時,屆時靜態區內和常量區內對象才會被釋放和回收(編譯器自動釋放)。所以應限制使用靜態類,靜態成員(靜態變數,靜態方法),常量,否則程式負荷高。
-
代碼區:存放函數體內的二進位代碼。
記憶體分配
值類型分配在棧上,引用類型分配在堆上,這個概念一般都是比較清楚的。但是遇到下麵的情況記憶體又是如何分配的呢?
Struct中嵌套Class對象
using System.Runtime.InteropServices;
namespace MyNamespace
{
namespace MyNamespace
{
public class B
{
public int x;
public byte y;
}
public struct A
{
public byte x;
public B b;
}
public class Test
{
static A a;
public static void Main()
{
a = new A()
{
x = 1,
b = new B() { x = 1, y = 1 },
};
System.Console.WriteLine(Marshal.SizeOf(a));
}
}
}
思考一下列印出來的值會是多少?
32位系統:
64位:
原因:
- 不同的編譯器(x86、x64)對應的指針大小不一樣。x86指針大小4個位元組,x64指針大小為8個位元組
- struct中存儲Class,存儲的是類的指針引用。整個Struct開闢在棧上,但是真正的類記憶體依舊是開闢在堆上。
- struct存在記憶體對齊的問題(提高訪問速度)。記憶體對齊會向記憶體最大的那個對齊,即測試代碼中,A.x會向(A.b的指針)大小對齊。
結論:Struct中嵌套Class引用對象,持有的是Class的引用指針。
Claas中嵌套Struct
using System.Runtime.InteropServices;
namespace MyNamespace
{
public class B
{
public int x;
public A y;
}
public struct A
{
public int x;
public byte y;
}
public class Test
{
static B b;
public static void Main()
{
b = new B()
{
x = 1,
y = new A() { x = 1, y = 1 },
};
}
}
}
通過對堆記憶體進行快照
32位:
64位:
出乎意料的記憶體大小!為什麼會是這樣的記憶體大小,我們先研究一下如果僅僅只有一個空對象,這個對象在記憶體中的大小是多少呢?我們通過查閱.Net官方文檔看一下,一個對象是如何分配的:
引用類型變數(如smallObj)以固定大小(4位元組)存儲在棧上,並指向在托管堆上分配的對象實例的地址。 smallObj的實例包含指向相應類型的MethodTable的TypeHandle(類型對象指針)和syncblk index(同步塊索引,用來做線程同步的,這裡就不詳細講了,大家可以去原文查看)。最後當一個類沒有定義任何實例欄位,它將產生4個位元組的開銷(用於分配到棧上來對他進行引用)。這樣我們就可以算出32位下一個空對象的記憶體大小了:4(syncblk index)+ 4(TypeHandle)+ 4(Instance Fields)= 12個位元組。同理在64位下將會是24個位元組。
返回到上面的測試用例,這時候我們就明白為什麼是這樣的記憶體分配大小了。32位下:基礎的12個位元組 + 4位元組(int)+ 4位元組(Struct指針) = 20個位元組。64位下的32個位元組留給大家思考是怎麼樣一個組成的呢?
總結
- Struct內部持有的是Class的指針
- Class內部持有的也是Struct的指針,但是這些都是開闢在堆上的
- Class需要分配syncblk index和TypeHandle,用來進行同步索引和類型查詢,在考慮Class的記憶體開銷的時候需要考慮進去。