前言
.net运行库通过垃圾回收器自动处理回收托管资源,非托管的资源需要手动编码处理。理解内存管理的工作原理,有助于提高应用程序的速度和性能。废话少说,切入正题。主要阐述的概念见下图:

#概念
記憶體
:又稱為虛擬記憶體,或
虛擬位址空間
,windows使用虛擬定址系統,在後台自動將可用的記憶體位址對應到硬體記憶體中的實際位址上,其結果是32位元處理器上的每個行程都可以使用4GB的內存,用來存放程式的所有部分,包括可執行程式碼(exe檔),程式碼載入的所有DLL,程式運行時使用的所有變數的內容。
記憶體堆疊 在進程的虛擬記憶體中,存在的一個變數的生存期必須嵌套的區域。
在進程的虛擬記憶體中,在方法退出後的很長一段時間內資料仍是可用的區域。
託管資源垃圾回收器在後台能自動處理的資源
非託管資源
需要手動編碼,透過析構函數,Finalize, IDisposable,Using等機製或方法處理的資源。 記憶體堆疊
值型別資料儲存在記憶體堆疊中,引用型別的實例位址值也放在記憶體堆疊中(見記憶體堆疊的討論),記憶體棧的工作原理,透過下面一段程式碼理解:
{ //block1开始 int a; //solve something {//block2开始 int b; // solve something else }//block2结束}//block1结束
以上程式碼注意2點: 1)C#中變數的作用域,遵循先宣告的後超出作用域,後宣告的先超出作用域,即b先釋放,a後釋放,釋放順序總是與它們分配記憶體的順序相反。
block2

block1
請看下面示意圖:
##在堆疊記憶體管理中,總是維護著一個堆疊指針,它總是指向站區域中下一個可用的位址,名字為
void Shout() { Monkey xingxing; //猴子类 xingxing = new Monkey(); }### 在這段程式碼中,假定兩個類別Monkey和AIMonkey,其中AIMonkey類別擴充了Monkey物件。 ### ### ###在這裡,我們稱Monkey為一個對象,稱xingxing為它的一個實例。 ### ### ### 首先,宣告了一個Monkey引用xingxing,在堆疊上給這個引用分配儲存空間,記住這僅是一個引用,而不是實際的Monkey物件。記住這一點很重要! ! ! ### 然後看下第2行程式碼:###
xingxing = new Monkey();### 它完成的操作:首先,它分配堆上的內存,以儲存Monkey對象,注意了! ! !這是一個真正的對象,它不是一個佔用4個位元組的位址! ! ! 假定Monkey物件佔用64個位元組,這64個位元組包含了Monkey實例的字段,和.NET中用於識別和管理Monkey類別實例的一些資訊。這64個位元組實在記憶體堆上分配的,假定記憶體堆上的位址1937~2000。 new運算子回傳一個記憶體位址,假定為997~1000,並賦值給xingxing。示意圖如下: ######

记住一点:
与内存栈不同的是,堆上的内存是向上分配的,由低地址到高地址。
从上面的例子中,可以看出建立引用实例的过程要比建立值变量的过程更复杂,系统开销更大。那么既然开销这么大,它到底优势何在呢?引用数据类型强大到底在哪里???
请看下面代码:
{//block1 Monkey xingxing; //猴子类 xingxing = new Monkey(); {//block2 Monkey jingjing = xingxing; //jingjing也引用了Monkey对象 //do something } //jinjing超出作用域,它从栈中删除 //现在只有xingxing还在引用Monkey}//xingxing超出作用域,它从栈中删除//现在没有在引用Monkey的了
把一个引用实例的值xingxing赋值予另一个相同类型的实例jingjing,这样的结果便是有两个引用内存中的同一个对象Monkey了。当一个实例超出作用域时,它会从栈中删除,但引用对象的数据还是保留在堆中,一直到程序终止,或垃圾回收器回收它位置,而只有该数据不再有任何实例引用它时,它才会被删除!
随便举一个实际应用引用的简单例子:
//从界面抓取数据放到list中List<Person> persons = getPersonsFromUI(); //retrieve these persons from DBList<person> personsFromDB = retrievePersonsFromDB(); //do something to personsFromDBgetSomethingToPersonsFromDB();
请问对personsFromDB的改变,能在界面上及时相应出来吗?
不能!
请看下面修改代码:
//从界面抓取数据放到list中List<Person> persons = getPersonsFromUI(); //retrieve these persons from DBList<Person> personsFromDB = retrievePersonsFromDB(); int cnt = persons.Count;for(int i=0;i<cnt;i++) { persons[i]= personsFromDB [i] ; } //do something to personsFromDBgetSomethingToPersonsFromDB();
修改后,数据能立即响应在界面上。因为persons与UI绑定,所有修改在persons上,自然可以立即响应。
这就是引用数据类型的强大之处,在C#.NET中广泛使用了这个特性。这表明,我们可以对数据的生存期进行非常强大的控制,因为只要保持对数据的引用,该数据就肯定位于堆上!!!
这也表明了基于栈的实例与基于堆的对象的生存期不匹配!
垃圾回收器 GC
内存堆上会有碎片形成,.NET垃圾回收器会压缩内存堆,移动对象和修改对象的所有引用的地址,这是托管的堆与非托管的堆的区别之一。
.NET的托管堆只需要读取堆指针的值即可,但是非托管的旧堆需要遍历地址链表,找出一个地方来放置新数据,所以在.NET下实例化对象要快得多。
堆的第一部分称为第0代,这部分驻留了最新的对象。在第0代垃圾回收过程中遗留下来的旧对象放在第1代对应的部分上,依次递归下去。。。
承上启下
以上部分便是对托管资源的内存管理部分,这些都是在后台由.NET自动执行的。下面看下非托管资源的内存管理,比如这些资源可能是UI句柄,network连接,文件句柄,Image对象等。.NET主要通过三种机制来做这件事。分别为析构函数、IDisposable接口,和两者的结合处理方法,以此实现最好的处理结果。下面分别看一下。
析构函数
C#编译器在编译析构函数时,它会隐式地把析构函数的代码编译为等价于Finalize()方法的代码,并确定执行父类的Finalize()方法。看下面的代码:
public class Person { ~Person() { //析构实现 } }
~Person()析构函数生成的IL的C#代码:
protected override void Finalize() { try { //析构实现 } finally { base.Finalize(); } }
放在finally块中确保父类的Finalize()一定调用。
C#析构函数要比C++析构函数的使用少很多,因为它的问题是不确定性。在销毁C++对象时,其析构函数会立即执行。但由于C#使用垃圾回收器,无法确定C#对象的析构函数何时执行。如果对象占用了 宝贵的资源,而需要尽快释放资源,此时就不能等待垃圾回收器来释放了。
第一次调用析构函数时,有析构函数的对象需要第二次调用析构函数,才会真正删除对象。如果频繁使用析构,对性能的影响非常大。
IDisposable接口
在C#中,推荐使用IDisposable接口替代析构函数,该模式为释放非托管资源提供了确定的机制,而不像析构那样何时执行不确定。
假定Person对象依赖于某些外部资源,且实现IDisposable接口,如果要释放它,可以这样:
class Person:IDisposable { public void Dispose() { //implementation } } Person xingxing = new Person();//dom somethingxingxing .Dispose();
上面代码如果在处理过程中出现异常,这段代码就没有释放xingxing,所以修改为:
Person xingxing = null;try{ xingxing = new Person(); //do something}finally{ if(xingxing !=null) { xingxing.Dispose(); } }
C#提供了一种语法糖,叫做using,来简化以上操作。
using(Person xingxing = new Person()) { // do something}
using在此处的语义不同于普通的引用类库作用。using用在此处的功能,仅仅是简化了代码,这种语法糖可以少用!!!
总之,实现IDisposable的对象,在释放非托管资源时,必须手动调用Dispose()方法。因此一旦忘记,就会造成资源泄漏。如下所示:
Image backImage = this.BackgroundImage; if (backImage != null) { backImage.Dispose(); SessionToImage.DeleteImage(_imageFilePath, _imageFileName); this.BackgroundImage = null; }
在上面那个例子中,backImage已经确定不再用了,并且backImage又是通过Image.FromFile(fullPathWay)从物理磁盘上读取的,是非托管的资源,所以需要Dispose()一下,这样读取Image的这个进程就被关闭了。如果忘记写backImage.Dispose();就会造成资源泄漏!
结合 析构函数和IDisposable这2种机制
一般情况下,最好的方法是实现两种机制,获得这两种机制的优点。因为正确调用Dispose()方法,同时把实现析构函数作为一种安全机制,以防没有调用Dispose()方法。请参考一种结合两种方法释放托管和非托管资源的机制:
public class Person:IDisposable { private bool isDisposed = false; //实现IDisposable接口 public void Dispose() { //为true表示清理托管和非托管资源 Dispose(true); //告诉垃圾回收器不要调用析构函数了 GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { //isDisposed: 是否对象已经被清理掉了 if(!isDisposed) { if(disposing) { //清理托管资源 } //清理非托管资源 } isDisposed = true; } ~Person() { //false:调用后只清理非托管资源 //托管资源会被垃圾回收器的一个单独线程Finalize() Dispose(false); } }
当这个对象的使用者,直接调用了Dispose()方法,比如
Person xingxing = new Person();//do somethingperson.Dispose();
此时调用IDisposable.Dispose()方法,指定应清理所有与该对象相关的资源,包括托管和非托管资源。
如果未调用Dispose()方法,则是由析构函数处理掉托管和非托管资源。
以上就是C#.NET:内存管理story的图文代码介绍的内容,更多相关内容请关注PHP中文网(www.php.cn)!

C#.NET生態系統提供了豐富的框架和庫,幫助開發者高效構建應用。 1.ASP.NETCore用於構建高性能Web應用,2.EntityFrameworkCore用於數據庫操作。通過理解這些工具的使用和最佳實踐,開發者可以提高應用的質量和性能。

如何將C#.NET應用部署到Azure或AWS?答案是使用AzureAppService和AWSElasticBeanstalk。 1.在Azure上,使用AzureAppService和AzurePipelines自動化部署。 2.在AWS上,使用AmazonElasticBeanstalk和AWSLambda實現部署和無服務器計算。

C#和.NET的結合為開發者提供了強大的編程環境。 1)C#支持多態性和異步編程,2).NET提供跨平台能力和並發處理機制,這使得它們在桌面、Web和移動應用開發中廣泛應用。

.NETFramework是一個軟件框架,C#是一種編程語言。 1..NETFramework提供庫和服務,支持桌面、Web和移動應用開發。 2.C#設計用於.NETFramework,支持現代編程功能。 3..NETFramework通過CLR管理代碼執行,C#代碼編譯成IL後由CLR運行。 4.使用.NETFramework可快速開發應用,C#提供如LINQ的高級功能。 5.常見錯誤包括類型轉換和異步編程死鎖,調試需用VisualStudio工具。

C#是一種由微軟開發的現代、面向對象的編程語言,.NET是微軟提供的開發框架。 C#結合了C 的性能和Java的簡潔性,適用於構建各種應用程序。 .NET框架支持多種語言,提供垃圾回收機制,簡化內存管理。

C#和.NET運行時緊密合作,賦予開發者高效、強大且跨平台的開發能力。 1)C#是一種類型安全且面向對象的編程語言,旨在與.NET框架無縫集成。 2).NET運行時管理C#代碼的執行,提供垃圾回收、類型安全等服務,確保高效和跨平台運行。

要開始C#.NET開發,你需要:1.了解C#的基礎知識和.NET框架的核心概念;2.掌握變量、數據類型、控制結構、函數和類的基本概念;3.學習C#的高級特性,如LINQ和異步編程;4.熟悉常見錯誤的調試技巧和性能優化方法。通過這些步驟,你可以逐步深入C#.NET的世界,並編寫高效的應用程序。

C#和.NET的關係是密不可分的,但它們不是一回事。 C#是一門編程語言,而.NET是一個開發平台。 C#用於編寫代碼,編譯成.NET的中間語言(IL),由.NET運行時(CLR)執行。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

SublimeText3漢化版
中文版,非常好用

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!

SublimeText3 Linux新版
SublimeText3 Linux最新版

WebStorm Mac版
好用的JavaScript開發工具

mPDF
mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),