亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解CLR的內(nèi)存分配和回收機(jī)制

 更新時(shí)間:2022年03月09日 16:08:51   作者:.NET開發(fā)菜鳥  
本文詳細(xì)講解了CLR的內(nèi)存分配和回收機(jī)制,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

一、CLR

CLR:即公共語言運(yùn)行時(shí)(Common Language Runtime),是中間語言(IL)的運(yùn)行時(shí)環(huán)境,負(fù)責(zé)將編譯生成的MSIL編譯成計(jì)算機(jī)可以識別的機(jī)器碼,負(fù)責(zé)資源管理(內(nèi)存分配和垃圾回收等)。

可能有人會提問:為什么不直接編譯成機(jī)器碼,而要先編譯成IL,然后在編譯成機(jī)器碼呢?

原因是:計(jì)算機(jī)的操作系統(tǒng)不同(分為32位和64位),接受的計(jì)算機(jī)指令也是不同的,在不同的操作系統(tǒng)中就要進(jìn)行不同的編譯,寫出的代碼在不同的操作系統(tǒng)中要進(jìn)行不同的修改。中間增加了IL層,不管是什么操作系統(tǒng),編譯生成的IL都是相同的,IL被不同操作系統(tǒng)的CLR編譯成機(jī)器碼,最終被計(jì)算機(jī)執(zhí)行。

JIT:即時(shí)編譯器,負(fù)責(zé)編譯成機(jī)器碼。

二、內(nèi)存分配

內(nèi)存分配:指程序運(yùn)行時(shí),進(jìn)程占用的內(nèi)存,由CLR負(fù)責(zé)分配。

值類型:值類型是struct的,例如:int、datetime等。

引用類型:即class,例如:類、接口,string等。

1、棧

棧:即線程棧,先進(jìn)后出的一種數(shù)據(jù)結(jié)構(gòu),隨著線程而分配,其順序如下:

看下面的例子:

定義一個(gè)結(jié)構(gòu)類型

public struct ValuePoint
{
    public int x;
    public ValuePoint(int x)
    {
         this.x = x;
    }
}

在方法里面調(diào)用:

//先聲明變量,沒有初始化  但是我可以正常賦值  跟類不同
ValuePoint valuePoint;
valuePoint.x = 123;

ValuePoint point = new ValuePoint();
Console.WriteLine(valuePoint.x);

內(nèi)存分配情況如下圖所示:

注意:

(1)、值類型分配在線程棧上面,變量和值都是在線程棧上面。

(2)、值類型可以先聲明變量而不用初始化。

2、堆

堆:對象堆,是進(jìn)程中獨(dú)立劃出來的一塊內(nèi)存,有時(shí)一些對象需要長期使用不釋放、對象的重用,這些對象就需要放到堆上。

來看下面的例子:

定義一個(gè)類

public class ReferencePoint
{
     public int x;
     public ReferencePoint(int x)
     {
           this.x = x;
     }
}

在代碼中調(diào)用:

ReferencePoint referencePoint = new ReferencePoint(123);
Console.WriteLine(referencePoint.x);

其內(nèi)存分配如下:

注意:

(1)、引用類型分配在堆上面,變量在棧上面,值在堆上面。

(2)、引用類型分配內(nèi)存的步驟:

  • a、new的時(shí)候去對象堆里面開辟一塊內(nèi)存,分配一個(gè)內(nèi)存地址。
  • b、調(diào)用構(gòu)造函數(shù)(因?yàn)樵跇?gòu)造函數(shù)里面可以使用this),這時(shí)才執(zhí)行構(gòu)造函數(shù)。
  • c、把地址引用傳給棧上面的變量。

3、復(fù)雜類型

a、引用類型里面嵌套值類型

先看下面引用類型的定義:

public class ReferenceTypeClass
{
        private int _valueTypeField;
        public ReferenceTypeClass()
        {
            _valueTypeField = 0;
        }
        public void Method()
        {
            int valueTypeLocalVariable = 0;
        }
}

在一個(gè)引用類型里面定義了一個(gè)值類型的屬性:_valueTypeField和一個(gè)值類型的局部變量:valueTypeLocalVariable,那么這兩個(gè)值類型是如何進(jìn)行內(nèi)存分配的呢?其內(nèi)存分配如下:

內(nèi)存分配為什么是這種情況呢?值類型不應(yīng)該是都分配在棧上面嗎?為什么一個(gè)是分配在堆上面,一個(gè)是分配在棧上面呢?

_valueTypeField分配在堆上面比較好理解,因?yàn)橐妙愋褪窃诙焉厦娣峙淞艘徽麎K內(nèi)存,引用類型里面的屬性也是在堆上面分配內(nèi)存。

valueTypeLocalVariable分配在棧上面是因?yàn)関alueTypeLocalVariable是一個(gè)全新的局部變量,調(diào)用方法的時(shí)候,會啟用一個(gè)線程去調(diào)用,線程棧來調(diào)用方法,然后把局部變量分配到棧上面。

b、值類型里面嵌套引用類型

先來看看值類型的定義:

public struct ValueTypeStruct
{
        private object _referenceTypeField;
        public ValueTypeStruct(int x)
        {
            _referenceTypeField = new object();
        }
        public void Method()
        {
            object referenceTypeLocalVariable = new object();
        }
}

在值類型里面定義了引用類型,其內(nèi)存是如何分配的呢?其內(nèi)存分配如下:

從上面的截圖中可以看出:值類型里面的引用類型的變量分配在棧上,值分配在堆上。

總結(jié):

1、方法的局部變量

根據(jù)變量自身的類型決定,與所在的環(huán)境沒關(guān)系。變量如果是值類型,就分配在棧上。變量如果是引用類型,內(nèi)存地址的引用存放在棧上,值存放在堆上。

2、對象是引用類型

其屬性/字段,都是在堆上分配內(nèi)存。

3、對象是值類型

其屬性/字段由自身的類型決定。屬性/字段是值類型就分配在棧上;屬性/字段是引用類型就分配在堆上。

上面的三種情況可以概括成下面一句話:

引用類型在任何時(shí)候都是分配在堆上;值類型任何時(shí)候都是分配在棧上,除非值類型是在引用類型里面。

4、String字符串的內(nèi)存分配

首先要明確一點(diǎn):string是引用類型。

先看看下面的例子:

string student = "大山";//在堆上面開辟一塊兒內(nèi)存  存放“大山”  返還一個(gè)引用(student變量)存放在棧上

其內(nèi)存分配如下圖所示:

這時(shí),在聲明一個(gè)變量student2,然后用student給student2賦值:

string student2 = student;

這時(shí)內(nèi)存是如何分配的呢?其內(nèi)存分配如下:

從上面的截圖中可以看出:student2被student賦值的時(shí)候,是在棧上面復(fù)制一份student的引用給student2,然后student和student2都是指向堆上面的同一塊內(nèi)存。

輸出student和student2的值:

Console.WriteLine("student的值是:" + student);
Console.WriteLine("student2的值是:"+student2);

結(jié)果:

從結(jié)果可以看出:student和student2的值是一樣的,這也能說明student和student2指向的是同一塊內(nèi)存。

這時(shí)修改student2的值:

student2 = "App";

這時(shí)在輸出student和student2的值,其結(jié)果如下圖所示:

從結(jié)果中可以看出:student的值保持不變,student2的值變?yōu)锳pp,為什么是這樣呢?這是因?yàn)閟tring字符串的不可變性造成的。一個(gè)string變量一旦聲明并初始化以后,其在堆上面分配的值就不會改變了。這時(shí)修改student2的值,并不會去修改堆上面分配的值,而是重新在堆上面開辟一塊內(nèi)存來存放student2修改后的值。修改后的內(nèi)存分配如下:

在看下面一個(gè)例子:

string student = "大山";
string student2 = "App";
student2 = "大山";
Console.WriteLine(object.ReferenceEquals(student,student2));

結(jié)果:

可能有人會想:按照上面講解的,student和student2應(yīng)該指向的是不同的內(nèi)存地址,結(jié)果應(yīng)該是false啊,為什么會是true呢?這是因?yàn)镃LR在分配內(nèi)存的時(shí)候,會查找是否有相同的值,如果有相同的值,就重用;如果沒有,這時(shí)在重新開辟一塊內(nèi)存。所以修改student2以后,student和student2都是指向同一塊內(nèi)存,結(jié)果輸出是true。

注意:

這里需要區(qū)分string和其他引用類型的內(nèi)存分配。其他引用類型的情況和string正好相反??聪旅娴睦?/p>

先定義一個(gè)Refence類,里面有一個(gè)int類型的屬性,類定義如下:

public class Refence
{
     public int Value { get; set; }
}

在Main()方法里面調(diào)用:

Refence r1 = new Refence();
r1.Value = 30;
Refence r2 = r1;
Console.WriteLine($"r2.Value的值:{r2.Value}");
r2.Value = 50;
Console.WriteLine($"r1.Value的值:{r1.Value}");
Console.ReadKey();

結(jié)果:

從運(yùn)行結(jié)果可以看出,如果是普通的引用類型,如果修改其他一個(gè)實(shí)例的值,那么另一個(gè)實(shí)例的值也會改變。正好與string類型相反。

三、內(nèi)存回收

值類型存放在線程棧上,線程棧是每次調(diào)用都會產(chǎn)生,用完自己就會釋放。

引用類型存放在堆上面,全局共享一個(gè)堆,空間有限,所以才需要垃圾回收。

CLR在堆上面是連續(xù)分配內(nèi)存的。

1、C#中的資源分為兩類:

a、托管資源

由CLR管理的存在于托管堆上的稱為托管資源,注意這里有2個(gè)關(guān)鍵點(diǎn),第一是由CLR管理,第二存在于托管堆上。托管資源的回收工作是不需要人工干預(yù)的,CLR會在合適的時(shí)候調(diào)用GC(垃圾回收器)進(jìn)行回收。

b、非托管資源

非托管資源是不由CLR管理,例如:Image Socket, StreamWriter, Timer, Tooltip, 文件句柄, GDI資源, 數(shù)據(jù)庫連接等等資源(這里僅僅列舉出幾個(gè)常用的)。這些資源GC是不會自動回收的,需要手動釋放。

2、托管資源

a、垃圾回收期(GC)

定期或在內(nèi)存不夠時(shí),通過銷毀不再需要或不再被引用的對象,來釋放內(nèi)存,是CLR的一個(gè)重要組件。

b、垃圾回收器銷毀對象的兩個(gè)條件

1)對象不再被引用----設(shè)置對象=null。

2)對象在銷毀器列表中沒有被標(biāo)記。

c、垃圾回收發(fā)生時(shí)機(jī)

1)垃圾回收發(fā)生在new的時(shí)候,new一個(gè)對象時(shí),會在堆中開辟一塊內(nèi)存,這時(shí)會查看內(nèi)存空間是否充足,如果內(nèi)存空間不夠,則進(jìn)行垃圾回收。

2)程序退出的時(shí)候也會進(jìn)行垃圾回收。

d、垃圾回收期工作原理

GC定期檢查對象是否未被引用,如果對象沒有被引用,則在檢查銷毀器列表。若在銷毀器列表中沒有標(biāo)記,則立即回收。若在銷毀器列表中有標(biāo)記,則開啟銷毀器線程,由該線程調(diào)用析構(gòu)函數(shù),析構(gòu)函數(shù)執(zhí)行完,刪除銷毀器列表中的標(biāo)記。

注意:

不建議寫析構(gòu)函數(shù),原因如下:

1)對象即使不用,也會在內(nèi)存中駐留很長一段時(shí)間。

2)銷毀器線程為單獨(dú)的線程,非常耗費(fèi)資源。

e、優(yōu)化策略

1)分級策略

a、首次GC前 全部對象都是0級。

b、第一次GC后,還保留的對象叫1級。這時(shí)新創(chuàng)建的對象就是0級。

c、垃圾回收時(shí),先查找0級對象,如果空間還不夠,再去找1級對象,這之后,還存在的一級對象就變成2級,0級對象就變成一級對象。

d、垃圾回收時(shí)如果0~2級都不夠,那么就內(nèi)存溢出了。

注意:

越是最近分配的,越是會被回收。因?yàn)樽罱峙涞亩际?級對象,每次垃圾回收時(shí)都是先查詢0級對象。

3、非托管資源

上面講的都是針對托管資源的,托管資源會被GC回收,不需要考慮釋放。但是,垃圾回收器不知道如何釋放非托管的資源(例如,文件句柄、網(wǎng)絡(luò)連接和數(shù)據(jù)庫連接)。托管類在封裝對非托管資源的直接或間接引用時(shí),需要制定專門的規(guī)則,確保非托管的資源在回收類的一個(gè)實(shí)例時(shí)會被釋放。

在定義一個(gè)類時(shí),可以使用兩種機(jī)制來自動釋放非托管的資源。這些機(jī)制常常放在一起實(shí)現(xiàn),因?yàn)槊糠N機(jī)制都為問題提供了略為不同的解決方法。這兩種機(jī)制是:

a、聲明一個(gè)析構(gòu)函數(shù)(或終結(jié)器),作為類的一個(gè)成員。

b、在類中實(shí)現(xiàn)System.IDisposable接口。

1)、析構(gòu)函數(shù)或終結(jié)器

析構(gòu)函數(shù)看起來類似于一個(gè)方法:與包含的類同名,但有一個(gè)前綴波形符號(~)。它沒有返回值,不帶參數(shù),也沒有訪問修飾符??聪旅娴囊粋€(gè)例子:

public class MyClass
{
        /// <summary>
        /// 析構(gòu)函數(shù)
        /// </summary>
        ~MyClass()
        {
            // 要執(zhí)行的代碼
        }
}

析構(gòu)函數(shù)存在的問題:

a、由于使用C#時(shí)垃圾回收器的工作方式,無法確定C#對象的析構(gòu)函數(shù)何時(shí)執(zhí)行。所以,不能在析構(gòu)函數(shù)中放置需要在某一時(shí)刻運(yùn)行的代碼,也不應(yīng)該寄希望于析構(gòu)函數(shù)會以特定順序?qū)Σ煌惖膶?shí)例調(diào)用。如果對象占用了寶貴而重要的資源,應(yīng)盡快釋放這些資源,此時(shí)就不能等待垃圾回收器來釋放了。

b、C#析構(gòu)函數(shù)的實(shí)現(xiàn)會延遲對象最終從內(nèi)存中刪除的時(shí)間。沒有析構(gòu)函數(shù)的對象會在垃圾回收器的一次處理中從內(nèi)存中刪除,但有析構(gòu)函數(shù)的對象需要兩次處理才能銷毀:第一次調(diào)用析構(gòu)函數(shù)時(shí),沒有刪除對象,第二次調(diào)用才真正刪除對象。

c、運(yùn)行庫使用一個(gè)線程來執(zhí)行所有對象的Finalize()方法。如果頻繁使用析構(gòu)函數(shù),而且使用它們執(zhí)行長時(shí)間的清理任務(wù),對性能的影響就會非常顯著。

注意:

在討論C#中的析構(gòu)函數(shù)時(shí),在低層的.NET體系結(jié)構(gòu)中,這些函數(shù)稱為終結(jié)器(finalizer)。在C#中定義析構(gòu)函數(shù)時(shí),編譯器發(fā)送給程序集的實(shí)際上是Finalize()方法,它不會影響源代碼。C#編譯器在編譯析構(gòu)函數(shù)時(shí),它會隱式地把析構(gòu)函數(shù)的代碼編譯為等價(jià)于重寫Finalize()方法的代碼,從而確保執(zhí)行父類的Finalize()方法。例如,下面的C#代碼等價(jià)于編譯器為~MyClass()析構(gòu)函數(shù)生成的IL:

protected override void Finalize()
{
       try
       {
            // 析構(gòu)函數(shù)中要執(zhí)行的代碼
       }
       finally
       {
            // 調(diào)用父類的Finalize()方法
            base.Finalize();
       }
}

2)、IDisposable接口

在C#中,推薦使用System.IDisposable接口替代析構(gòu)函數(shù)。IDisposable接口定義了一種模式,該模式為釋放非托管的資源提供了確定的機(jī)制,并避免產(chǎn)生析構(gòu)函數(shù)固有的與垃圾回收器相關(guān)的問題。IDisposable接口聲明了一個(gè)Dispose()方法,它不帶參數(shù),返回void。例如:

public class People : IDisposable
{
        public void Dispose()
        {
            this.Dispose();
        }
}

Dispose()方法的實(shí)現(xiàn)代碼顯式地釋放由對象直接使用的所有非托管資源,并在所有也實(shí)現(xiàn)了IDisposable接口的封裝對象上調(diào)用Dispose()方法。這樣,Dispose()方法為何時(shí)釋放非托管資源提供了精確的控制。

3)、using語句

C#提供了一種語法,可以確保在實(shí)現(xiàn)了IDisposable接口的對象的引用超出作用域時(shí),在該對象上自動調(diào)用Dispose()方法。該語法使用了using關(guān)鍵字來完成此工作。例如:

using (var people = new People())
{
      // 要處理的代碼
}

4)、析構(gòu)函數(shù)和Dispose()的區(qū)別

a、析構(gòu)函數(shù)

析構(gòu)函數(shù) 主要是用來釋放非托管資源,等著GC的時(shí)候去把非托管資源釋放掉 系統(tǒng)自動執(zhí)行。GC回收的時(shí)候,CLR一定調(diào)用的,但是可能有延遲(釋放對象不知道要多久呢)。

b、Dispose()

Dispose() 也是釋放非托管資源的,主動釋放,方法本身是沒有意義的,我們需要在方法里面實(shí)現(xiàn)對資源的釋放。GC的時(shí)候不會調(diào)用Dispose()方法,而是使用對象時(shí),使用者主動調(diào)用這個(gè)方法,去釋放非托管資源。

5)、終結(jié)器和IDisposable接口的規(guī)則

a、如果類定義了實(shí)現(xiàn)IDisposable的成員(類里面的屬性實(shí)現(xiàn)了IDisposable接口),那么該類也應(yīng)該實(shí)現(xiàn)IDisposable接口。

b、實(shí)現(xiàn)IDisposable并不意味著也應(yīng)該實(shí)現(xiàn)一個(gè)終結(jié)器。終結(jié)器會帶來額外的開銷,因?yàn)樗枰獎(jiǎng)?chuàng)建一個(gè)對象,釋放該對象的內(nèi)存,需要GC的額外處理。只在需要時(shí)才應(yīng)該實(shí)現(xiàn)終結(jié)器,例如。發(fā)布本機(jī)資源。要釋放本機(jī)資源,就需要終結(jié)器。

c、如果實(shí)現(xiàn)了終結(jié)器,也應(yīng)該實(shí)現(xiàn)IDisposable接口。這樣,本機(jī)資源可以早些釋放,而不僅是在GC找出被占用的資源時(shí),才釋放資源。

d、在終結(jié)器的實(shí)現(xiàn)代碼中,不能訪問已經(jīng)終結(jié)的對象。終結(jié)器的執(zhí)行順序是沒有保證的。

e、如果所使用的一個(gè)對象實(shí)現(xiàn)了IDisposable接口,就在不再需要對象時(shí)調(diào)用Dispose方法。如果在方法中使用這個(gè)對象,using語句比較方便。如果對象是類的一個(gè)成員,那么類也應(yīng)該實(shí)現(xiàn)IDisposable接口。

到此這篇關(guān)于詳解CLR的內(nèi)存分配和回收機(jī)制的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • C#實(shí)現(xiàn)ini文件讀寫操作

    C#實(shí)現(xiàn)ini文件讀寫操作

    這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)ini文件的讀寫操作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-08-08
  • c#閉包使用方法示例

    c#閉包使用方法示例

    這篇文章主要介紹了如何使用C#的閉包功用,例子很簡單,大家參考使用吧
    2013-11-11
  • C#調(diào)用C動態(tài)鏈接庫的實(shí)現(xiàn)

    C#調(diào)用C動態(tài)鏈接庫的實(shí)現(xiàn)

    動態(tài)鏈接庫是不能直接執(zhí)行的,也不能接收消息,它只是一個(gè)獨(dú)立的文件,本文主要介紹了C#調(diào)用C動態(tài)鏈接庫的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01
  • C#繪制曲線圖的方法

    C#繪制曲線圖的方法

    這篇文章主要介紹了C#繪制曲線圖的方法,以完整實(shí)例形式較為詳細(xì)的分析了C#進(jìn)行曲線繪制的具體步驟與相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-10-10
  • 解析C#彩色圖像灰度化算法的實(shí)現(xiàn)代碼詳解

    解析C#彩色圖像灰度化算法的實(shí)現(xiàn)代碼詳解

    本篇文章是對C#中彩色圖像灰度化算法的實(shí)現(xiàn)進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • C#?基于NAudio實(shí)現(xiàn)對Wav音頻文件剪切(限PCM格式)

    C#?基于NAudio實(shí)現(xiàn)對Wav音頻文件剪切(限PCM格式)

    本文主要介紹了C#基于NAudio工具對Wav音頻文件進(jìn)行剪切,可以將一個(gè)音頻文件剪切成多個(gè)音頻文件(限PCM格式),感興趣的小伙伴可以學(xué)習(xí)一下
    2021-11-11
  • 逐步講解快速排序算法及C#版的實(shí)現(xiàn)示例

    逐步講解快速排序算法及C#版的實(shí)現(xiàn)示例

    快速排序在時(shí)間復(fù)雜度同為O(N*logN)的幾種排序方法中效率較高,因而比較常用,接下來這里就來逐步講解快速排序算法及C#版的實(shí)現(xiàn)示例
    2016-06-06
  • C# 利用StringBuilder提升字符串拼接性能的小例子

    C# 利用StringBuilder提升字符串拼接性能的小例子

    一個(gè)項(xiàng)目中有數(shù)據(jù)圖表呈現(xiàn),數(shù)據(jù)量稍大時(shí)顯得很慢,在使用了StringBuilder后效果提升很明顯,下面有例子
    2013-07-07
  • 基于C#對用戶密碼使用MD5加密與解密

    基于C#對用戶密碼使用MD5加密與解密

    C#中常涉及到對用戶密碼的加密于解密的算法,其中使用MD5加密是最常見的的實(shí)現(xiàn)方式。本文總結(jié)了通用的算法并結(jié)合了自己的一點(diǎn)小經(jīng)驗(yàn),分享給大家
    2015-12-12
  • C#圖像線性變換的方法

    C#圖像線性變換的方法

    這篇文章主要介紹了C#圖像線性變換的方法,涉及C#操作圖像線性變換的相關(guān)技巧,需要的朋友可以參考下
    2015-04-04

最新評論