黑馬公開課——執行原理與GC學習筆記

jerrysun發表於2021-09-09

.NET Framework 程式的執行原理

.NET Framework的組成:
(1)基礎類庫(BCL):使用執行緒的類來完成程式設計,對於不存在的類,就自己編寫;
(2)編譯工具:將原始檔,編譯成“程式集”(exe或dll等)[.NET環境中,MSIL=CIL=IL]
(3)公共語言執行時(CLR):執行前檢測、編譯;執行到了某個方法時才編譯這個方法的程式碼[即時編譯器(JIT)]
編譯過程:.NET原始碼(C#)——>透過C#編譯器編譯成程式集[程式集中包括:後設資料(一個表,顯示了程式中有什麼成員,類,欄位,方法等),IL程式碼等資源]
執行例子:
(1)原始碼如下:
using System;

namespace SimpleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            Console.WriteLine("ByeBye World!");
        }
    }
}
——>程式都是從上到下執行,執行Main方法前要檢查Main方法中有什麼類,為類分配一個臨時的記憶體空間表。該類(各個類)中每一個方法,都有一個對應的地址(此時地址還是空的)。
——>當執行到第一個Console.WriteLine時:CLR中的JIT找到對應的IL程式碼,並將其編譯成機器碼並最佳化,將編譯好的程式碼放到記憶體塊中,會產生一地址,例如:0x000100
——>開始執行該WriteLine方法實體——>第二次執行WriteLine時:CLR會檢查之前的方法表,如果已經存在地址,則不再編譯執行。
記憶體分配:
執行緒棧:資料地址從高位向低位存放;
託管堆:資料地址從低位向高位存放;
.NET Framework 垃圾回收機制 GC

GC(Garbage Collection)垃圾收集,指的是在.net中垃圾記憶體收集的機制。

GC原理:當程式碼建立的時候,物件在記憶體中“連續”分配;當執行一次垃圾回收以後,失去引用的物件將會被釋放;而保持引用的物件會重新“排序”(0代->1代)[重新排序後記憶體依舊是連續的]

以下是轉載自軒脈刃de刀光劍影的部落格 原文地址: http://www.cnblogs.com/yjf512/archive/2010/09/14/1825518.html

首先要了解的幾點:

  1. 在.net中,託管程式碼的記憶體管理是自動的,由GC進行管理。但是對於非託管程式碼,.net就無法自動管理了。

  2. CLR執行時候,記憶體分為“託管堆”和“棧”兩個部分。其中,棧是用於儲存值型別的資料,託管堆是用於儲存引用型別的變數。其中託管堆是GC處理的記憶體部分。

  3. 程式中每個執行緒都有自己的堆疊。

對於託管程式碼的GC原理解讀:

垃圾判定:

回收垃圾首先要知道什麼是垃圾,一個變數如果在其生存期內的某一時刻已經不再被引用,那麼,這個物件就有可能成為垃圾。

public static void Main()
              {
                 string sGarbage = "I'm here";
           //下面的程式碼沒有再引用s,它已經成為垃圾物件---當然,這樣的程式碼本身也是垃圾;
            //此時如果執行垃圾收集,則sGarbage可能已經魂歸西天
                  Console.WriteLine("Main() is end");
              }

 

 

物件代齡:

GC認為,越晚建立的物件越短命,所以,其引入一個“代齡”的概念來劃分物件生存級別

這個代齡劃分機制簡要來說是一代新人換舊人:

 圖片描述

CLR初始化後的第一批被建立的物件被列為0代物件。CLR會為0代物件設定一個容量限制,當建立的物件大小超過這個設定的容量上限時,GC就會開始工作,工作的範圍是0代物件所處的記憶體區域,然後開始搜尋垃圾物件,並釋放記憶體。當GC工作結束後,倖存的物件將被列為第1代物件而保留在第1代物件的區域內。此後新建立的物件將被列為新的一批0代物件,直到0代的記憶體區域再次被填滿,然後會針對0代物件區域進行新一輪的垃圾收集,之後這些0代物件又會列為第1代物件,併入第1代區域內。第1代區域起初也會被設上一個容量限制值,等到第1代物件大小超過了這個限制之後,GC就會擴大戰場,對第1代區域也做一次垃圾收集,之後,又一次倖存下來的物件將會提升一個代齡,成為第2代物件。


    可見,有一些物件雖然符合垃圾的所有條件,但它們如果是第1代(甚至是第2代老臣)物件,並且第1代的分配量還小於被設定的限制值時,這些垃圾物件就不會被GC發現,並且可以繼續存活下去。

 

 

對於非託管程式碼,GC不能自動收集垃圾,需要的方法有兩種:1,重寫讓GC自動呼叫的Finalize方法。 2,實現IDispose提供給我們顯示呼叫的方法Dispose()

  1. Finalize

~ClassName() {//釋放你的非託管資源}

Finalize是由GC負責呼叫,是一種自動釋放的方式。

問:為什麼說實現了Finalize方法的物件必需等兩次GC才能被完全釋放?

 圖片描述

Msdn中的解釋:實現 Finalize 方法或解構函式對效能可能會有負面影響,因此應避免不必要地使用它們。用 Finalize 方法回收物件使用的記憶體需要至少兩次垃圾回收。當垃圾回收器執行回收時,它只回收沒有終結器的不可訪問物件的記憶體。這時,它不能回收具有終結器的不可訪問物件。它改為將這些物件的項從終止佇列中移除並將它們放置在標為準備終止的物件列表中。該列表中的項指向託管堆中準備被呼叫其終止程式碼的物件。垃圾回收器為此列表中的物件呼叫 Finalize 方法,然後,將這些項從列表中移除。後來的垃圾回收將確定終止的物件確實是垃圾,因為標為準備終止物件的列表中的項不再指向它們。在後來的垃圾回收中,實際上回收了物件的記憶體。

        

         第一次的GC做的事情是:1將有終結器的物件放到準備終結列表中,並執行Finalize方法。2 實際刪除物件記憶體。

  1. Dispose

Dispose是提供給我們顯示呼叫的方法。由於對Dispose的實現很容易出現問題,所以在一些書籍上(如《Effective C#》和《Applied Microsoft.Net Framework Programming》)給出了一個特定的實現模式:

class DisposePattern :IDisposable
    {
        private System.IO.FileStream fs = new System.IO.FileStream("test.txt", System.IO.FileMode.Create);

        ~DisposePattern()
        {
            Dispose(false);
        }       

        IDisposable Members#region IDisposable Members

        public void Dispose()
        {
            //告訴GC不需要再呼叫Finalize方法,
            //因為資源已經被顯示清理
            GC.SupdivssFinalize(this);

            Dispose(true);
        }

        #endregion
               
        protected virtual void Dispose(bool disposing)
        {
            //由於Dispose方法可能被多執行緒呼叫,
            //所以加鎖以確保執行緒安全
            lock (this)
            {
                if (disposing)
                {
                    //說明物件的Finalize方法並沒有被執行,
                    //在這裡可以安全的引用其他實現了Finalize方法的物件
                }

                if (fs != null)
                {
                    fs.Dispose();
                    fs = null; //標識資源已經清理,避免多次釋放
                }
            }
        }
    }

注: 關鍵字using()中包含的變數就需要實現了IDispose藉口,當出了using的範圍的時候會自動使用Dispose方法。

 

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/151/viewspace-2811718/,如需轉載,請註明出處,否則將追究法律責任。

相關文章