程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 用C#來顯示垃圾收集過程的視覺效果

用C#來顯示垃圾收集過程的視覺效果

編輯:關於C#

廢話不多說了,本人是搞Web方向的,C/S不太熟悉,先看界面圖(比較粗糙),這裡僅僅是從一個視覺的 效果來初步顯示GC相對應的操作(簡單的效果顯示,並不是真正的GC內幕,那個我也不懂)

基本概念

對象的生成過程(newobj指令)

1:計算類型(包括基類)所有字段的字節總數

2: 字節總數再加上對象開銷字段字節數(相加為:對象所需的字節數)。每個對象包含2個開銷字段 :類型對象指針以及同步塊索引。WIN32中,各占32位,WIN64中,各占64位。

3:CLR檢測托管堆中是 否有足夠的空間滿足對象所需的字節數。如果滿足,對象將被分配在NextObjPtr指針指示的地方,實例構造器 被調用,(new操作)返回對象的內存地址。指針NextObjPtr越過對象所在的區域,指示下一個新建對象在托 管堆中的地址。如果不滿足,進行垃圾收集。

每一個應用程序都有一組根Root。一個根是一 個存儲地址,包含一個指向類型對象的指針。

該指針有2種形式:(1)指向托管堆中的一個對象。(2 )設為null。

根包括靜態字段,方法參數,局部變量,CPU寄存器。

對象的代

托管堆中 ,對象的代大概為0代,1代,2代,相應的內存容量為256K,2M,10M。當然,垃圾收集器也會自動調整預算容 量。

終結操作和釋放模式

終結操作(Finalize()方法)可以確保托管對象在釋放內存的同時 不會洩露本地資源,但是不能確定它在何時被調用。

釋放模式(Dispose()方法):當對象不再被使用 的時候顯示的釋放掉它所占有的資源。 (更多控制)注:可以用來控制在對象生命周期內資源的重復利用, 例如connection資源不一定每次操作都要關閉。

下序的代碼顯示了GC.Collect()方法將使Finalize( )方法被調用:

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
    
--> public static class Program
    {
        static void Main(string[] args)
        {
            new GCNotice();
            Timer timer = new Timer(TimerCallBack,null,0,2000);
            Console.ReadLine();
            timer.Dispose();
        }
        private static void TimerCallBack(object obj)  
        {
            Console.WriteLine("GC START Time:"+DateTime.Now.ToString());
            GC.Collect();
            Console.WriteLine("GC END Time:" + DateTime.Now.ToString());
        }
    }
    
    sealed class GCNotice
    {
       ~GCNotice(){
           Console.Beep();
           Console.WriteLine("*********GCNotice FINALIZE():"+DateTime.Now.ToString());
           if(!AppDomain.CurrentDomain.IsFinalizingForUnload())
           {
             new GCNotice();
           }
       }
    }

~GCNotice(){
}  析構函數(C++)就是我們所說的終結操作(與C++不同),也 就是Finalize()方法。在下列事件中將觸發:

(1):第0代對象充滿時(垃圾收集)。

(2) :代碼顯示調用System.GC.Collect()。

(3):Windoms報告內存不足。

(4):CLR卸載應用 程序域。

(5):CLR關閉。

一般情況下,如果一個類型中本地資源需求比較大,建議使用 HandleCollector來促進GC.Collect()執行(釋放資源)。

代碼 
    
    
    
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
    
-->namespace System.Runtime.InteropServices
{
    // 摘要:
    //     跟蹤未處理的句柄,並在達到指定阈值時強制執行垃圾回收。
    public sealed class HandleCollector
    {
        // 摘要:
        //     使用一個名稱以及一個阈值(在達到該值時開始執行句柄回收)初始化 

System.Runtime.InteropServices.HandleCollector
        //     類的新實例。
        //
        // 參數:
        //   name:
        //     回收器的名稱。此參數允許您為跟蹤句柄類型的回收器分別命名。
        //
        //   initialThreshold:
        //     指定何時開始執行回收的值。
        //
        [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen 

image boundaries")]
        public HandleCollector(string name, int initialThreshold);
        // 摘要:  
        //     增加當前句柄計數。
              public void Add();
        //     減少當前句柄計數。
        //
              public void Remove();
      }
}

public HandleCollector(string name, int initialThreshold);     //組合到類型 中,實例化

public void Add();//構造函數中運用
public void Remove();//析構函數中運用

終結鏈表與終結可達隊列

創建一個新對象時,如果對象的類型定義了Finalize()方法,那麼指 向該對象的指針將被放到終結鏈表中。終結鏈表上的每一個條目都引用著一個對象,指示GC在回收這些對象之 前調用它們的Finalize()方法。

主要過程如下

好了,概念的東西不再介紹了,本人思路如下:

(一)准備工作:創建一個DataObject類型(模擬一個對象實體),DataObjects(對象集合), DataObjectManager(對象集合管理)。

(二)初始化一個屬性值為隨機值的 DataObject對象

(三)判斷托管堆0代內存是否充足,如果滿足則分配對象內存(模擬)(如果有終結方法,則添加引用到終 結鏈表中)。如果不滿足,進行垃圾收集。

(四)垃圾收集操作:細分為0,1,2代的比較判斷與操作

(五)收集後內容的顯示,調用面板panel的refresh()方法。

(六)隨機修改原對象集合中的 對象的值 HasRoot為false(後來添加的),標識無根。

(一) 准備工作

先自創建一個類,主 要是以該對象來作為操作的。

Code highlighting produced by Actipro CodeHighlighter 

(freeware)
http://www.CodeHighlighter.com/
    
--> public class DataObject : Jasen.GCShow.IDataObject
    {
        public Boolean HasFinalizeMethod { get; set; }
        public Boolean HasRoot { get; set; }
        public Int32 Number { get ;set; }
        public System.Drawing.Color Color { get;set; }
        public String OXString{
            get{
                //return (HasRoot ? "R" : "0") + (HasFinalizeMethod ? "F" : ""); 
                return (HasFinalizeMethod ? "[F]" : "[!F]");
            }
        }
             
        public String Name { get; set; }
        public String NiceName { get; set; }
        public Int32 Generation { get; set; }
    }

然後就是該類對象集合,實現遍歷以及索引:

Code highlighting produced by 

Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
    
-->  public class DataObjects : IEnumerable, Jasen.GCShow.IDataObjects
    {
        private List<DataObject> objectList=null;
        public DataObjects(List<DataObject> objects) {
            this.objectList = objects;
        }
        public DataObjects(){
        this.objectList=new  List<DataObject>();
        }
        public void Add(DataObject item)
        {
            if(!objectList.Contains(item)){
            objectList.Add(item);
            }
        }
        public void Remove(DataObject item)
        {
            if (objectList.Contains(item)){
                objectList.Remove(item);
            }
        }
        public int Count()
        {
            if(objectList==null){
                return 0;
            }
            return objectList.Count;
        }
        public DataObject this[int i]{
            get{
                if (objectList != null && objectList.Contains(objectList[i])){
                    return objectList[i];
                }
                else{
                    return default(DataObject);
                }
            }
            set{
                objectList[i] = value;
            }
        }
        #region IEnumerable 成員
    
        IEnumerator IEnumerable.GetEnumerator()
        {
             for (int i = 0; i < objectList.Count(); i++)
             {
                yield return this[i];
             }
        }
        #endregion
    }

其次就是該對象集合的管理類,負責所有對象的ItemCollection,以及0,1,2代對象集合,以 及終結鏈表,終結可達隊列

Code highlighting produced by Actipro CodeHighlighter 

(freeware)
http://www.CodeHighlighter.com/
    
--> 1  public class DataObjectManager : Jasen.GCShow.IDataObjectManager 
    {
        DataObjects items = new DataObjects();
        Queue<DataObject> freachableQueue = new Queue<DataObject>();
        DataObjects finalizeTable = new DataObjects();
    
        public DataObjects ItemsCollection 
        {
            get { return items; }
            set { items = value; }
        }
        public DataObjects ZeroGenerationCollection
        {
            get { return GetCollection(0); }
        }
    
        public DataObjects GetCollection(int generation) 
        {
           if (ItemsCollection.Count() == 0) return null;
           DataObjects generationObjects = new DataObjects();
           foreach(DataObject obj in ItemsCollection){
              if(obj.Generation==generation){
                  generationObjects.Add(obj);
              }
           }
            return generationObjects;
        }
        public DataObjects OneGenerationCollection
        {
            get { return GetCollection(1); }
        }
        public DataObjects TwoGenerationCollection 
        {
            get { return GetCollection(2); }
        }
        public DataObjects FinalizeTable  
        {
            get { return finalizeTable; }
            set { finalizeTable = value; }
        }
        public Queue<DataObject> FreachableQueue 
        {
            get { return freachableQueue; }
            set { freachableQueue = value; }  
        }  
    }

(二)初始化一個屬性值為隨機值的 DataObject對象

通過隨機設置類的值來實例化一 個對象

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
    
-->DataObject item = new DataObject()
            {
                HasFinalizeMethod = Randoms.GetRandomBoolen(0.3),
                HasRoot = Randoms.GetRandomBoolen(0.6),
                Color = Randoms.GetRandomColor(),
                Number = Randoms.RandomNum(1, 3),
                Name = Guid.NewGuid().ToString(),
                NiceName = Randoms.AddNum().ToString(),
                Generation = 0 // 默認為0代
            };

以上的值大部分是隨機的,不確定的,比如下面的方法----->返回隨機比例為 rate(比如0.3)的true值,它等價於有30%的概率返回true,70%概率返回false,

Code 

highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
    
--> /// <summary>
        /// 返回隨機比例為rate的 true值
        /// </summary>
        /// <param name="rate"></param>
        /// <returns></returns>
        public static Boolean GetRandomBoolen(double rate) {
            if (rate < 0 || rate > 1) throw new  ArgumentOutOfRangeException("rate must be 

between 0 to 1");
            Random random = new Random((int)DateTime.Now.Ticks);
            System.Threading.Thread.Sleep(100);
            if(random.Next(0,10000)>=10000*(1-rate)){
                return true;
            }
            return false;
        }

隨機顏色如下

Code highlighting produced by Actipro CodeHighlighter 

(freeware)
http://www.CodeHighlighter.com/
    
--> public static Color GetRandomColor()
        {
            Random randomFirst = new Random((int)DateTime.Now.Ticks); 
            System.Threading.Thread.Sleep(300);
            Random randomSencond = new Random((int)DateTime.Now.Ticks);
            System.Threading.Thread.Sleep(300);
            Random randomThird = new Random((int)DateTime.Now.Ticks);
            int intRed = randomFirst.Next(256);
            int intGreen = randomSencond.Next(256);
            int intBlue = randomThird.Next(256);
            return Color.FromArgb(intRed, intGreen, intBlue);
        }

(三)判斷托管堆0代內存是否充足

判斷的大概過程如下:

Code 

highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
    
--> #region newobject指令過程
        private Int32 CHARTOTALNUMBER = 0;
        private void NewObjectOperationProcess(DataObject item){
            //計算類型所有字段的字節總數
            CHARTOTALNUMBER = CountTypeCharTotalNumber(item);
            //計算2個開銷字段:類型對象指針,同步塊索引   WIN32--32位×2=64位=8字節
            CountObjectExtraCharNumber();
            //判斷0代對象內存(256K)是否含有所需的字節數  (長度)
            Boolean isEnough= CheckZeroGenerationHasEnoughChars();
            //計算新建對象在托管堆中的地址     (長度)
            if (isEnough)
            {
                RefreshZeroGenenrationAndFinalizeTable(item);
            }
            else { 
            //回收垃圾
                GCCollect(item);
            }
        }

如果托管堆0代內存充足,那麼顯示如下:

上面顯示的是對象含有根,沒有終結方法。我們來看一張含有終結方法的圖,含有終結方法的對 象會被添加引用到終結鏈表中,如下:

(四)垃圾收集操作:細分為0,1,2代的比較判斷與操作

(1)處理托管堆0代對象的主 要操作如下:

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
    
-->  private void GCSystemOperation()
        {
            ClearFreachableQueue();
            DataObjects temps = new DataObjects();
    
            //清理沒根的沒終結方法的0代對象  0代對象 +1 (清除)
            DataObjects list = manager.ZeroGenerationCollection;
            if (list == null) return;
            foreach (DataObject obj in list)
            {
                //如果對象沒有根 並且沒有終結方法
                if (obj.HasRoot == false && obj.HasFinalizeMethod == false){
                    manager.ItemsCollection.Remove(obj);
                }
                else
                {
                    temps.Add(obj);
                    //obj.Generation++;
                }
            }
            if(temps.Count()>0){
                int tempsLength=CountSize(temps);
                int oneGenerationCurrentLength = CountSize(manager.OneGenerationCollection);
                Boolean isOneGenerationEnough = (SystemConst.OneGenerationLength-

oneGenerationCurrentLength > tempsLength)?true:false;
                if (isOneGenerationEnough)
                {
                    GenerationAddOne(temps);
                }
                else {
                    //處理托管堆1代對象
                    MessageBox.Show("處理托管堆1代對象!");
                    HandleOneGeneration(temps);              
                }
            }
        }

當一直添加對象時,達到如下情況:

我們不知道下一個對象的內存大小,很有下一次就會可能發生垃圾收集。如下圖所示,當托管堆0 代對象內存容量不足時,會觸發垃圾收集:

其中先清理可達隊列中的數據對象,(含有Finalize()終結方法並且無根,一般情況為在第1次收 集時將終結鏈表中的指針移動至終結可達隊列中,這樣可達隊列中才有指針。第2次收集就會將可達隊列中的 指針清理)

執行下列代碼:

Code highlighting produced by Actipro CodeHighlighter 

(freeware)
http://www.CodeHighlighter.com/
    
--> 1  private void ClearFreachableQueue()
        {
            //清理終結可達隊列中的對象 沒根 有終結方法 (清除)   一般為清理上次收集數據
            while (manager.FreachableQueue.Count > 0){
                DataObject obj = manager.FreachableQueue.Dequeue();
                manager.ItemsCollection.Remove(obj);
            }
            MessageBox.Show("清理可達隊列對象");
            //終結鏈表中的數據  --》可達隊列 
            foreach (DataObject item in manager.FinalizeTable){
                if (item.HasRoot == false){
                    manager.FreachableQueue.Enqueue(item);
                }
            }
            MessageBox.Show("將終結鏈表中的可達對象移動至可達隊列");
            foreach (DataObject obj in manager.FreachableQueue){
                manager.FinalizeTable.Remove(obj);
            }
            MessageBox.Show("移除終結鏈表中包含的可達隊列對象");
        }

顯然,將終結鏈表的數據移動到可達隊列後,然後再移除終結鏈表包含的可達隊列的指針 ,操作後如下:

(2)處理托管堆1代對象的主要操作如下:

Code highlighting produced by Actipro 

CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
    
--> private void HandleOneGeneration(DataObjects temps)
        {
            DataObjects currentTempObjects = new DataObjects();
            foreach(DataObject obj in manager.OneGenerationCollection){
                if (obj.HasRoot == false && obj.HasFinalizeMethod == false){
                    manager.ItemsCollection.Remove(obj);
                }
                else {
                    currentTempObjects.Add(obj);
                }
            }
            if (currentTempObjects.Count() > 0)
            {
                Boolean enough = CheckTwoGenerationEnough(currentTempObjects);
                if (enough)
                {
                    MessageBox.Show("托管堆2代內存充足----》托管堆1代對象  對象代+1");
                    GenerationAddOne(currentTempObjects);
                }
                else {
                    MessageBox.Show("托管堆2代內存不足----》處理");
                    HandleTwoGeneration(currentTempObjects);
                }
            }
            MessageBox.Show("托管堆0代對象  對象代+1");
            GenerationAddOne(temps);
        }

繼續創建新的對象:

發現越來越多的對象在托管堆1代中存在。

一直增加,當托管堆0代對象內存不足,並且托管堆1代對象內存也不足時候,將導致1代對象的代 +1;其中也包括1代對象的清理工作,移除無根的對象。

(3)處理托管堆2代對象的主要操作如下:

Code highlighting produced by Actipro 

CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
    
--> 1  private void HandleTwoGeneration(DataObjects currentTempObjects)
        {
            Boolean enough = CheckTwoGenerationEnough(currentTempObjects);
            if (enough){
                GenerationAddOne(currentTempObjects);
            }
            else {
                MessageBox.Show("托管堆2代對象內存滿了,清理托管堆2代無根對象");
                ClearGenerationUnusefulObject(manager.TwoGenerationCollection);
                if (CheckGenerationEnough(currentTempObjects, manager.TwoGenerationCollection, 

SystemConst.TwoGenerationLength)){
                    MessageBox.Show("托管堆1代對象  對象代+1");
                    GenerationAddOne(currentTempObjects);
                }
                else{ 
                    ClearToEmpty(); //托管堆對象全部清理
                }
            }   
        }

(五)垃圾收集後內容的顯示,調用面板panel的refresh()方法。

例如托管堆0代 的面板刷新

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
    
-->  private void panelZeroGenenration_Paint(object sender, PaintEventArgs e)
        {
            if (manager.ItemsCollection.Count() == 0) { return; }
            DataObjects list = manager.ZeroGenerationCollection;
            if(list==null)return;
            Graphics graphics = e.Graphics;
            FillRectangle(graphics, list, true,true );
        }

相應的面板繪制如下,采用累加來計算繪制的坐標(left)

Code highlighting 

produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
    
--> 1   private void FillRectangle(Graphics graphic, DataObjects list,Boolean 

markArrows,Boolean markOX) 
        {
            float left = 0,width = 0,top = 0,height = 20,rams=-15;          
            for (int i = 0; i < list.Count(); i++){
                int sum = 0;
                if (i != 0) {
                    for (int z = 0; z < i; z++){
                        sum += list[z].Number;//i-1次
                    }
                }
                left = sum * SystemConst.GraphicLength;
                width = SystemConst.GraphicLength * list[i].Number;              
                graphic.FillRectangle(new SolidBrush(list[i].Color), left, top, width, height);
                graphic.FillRectangle(new SolidBrush(Color.Red), left, top, 2, height);          

   
                graphic.DrawString(("[" + list[i].NiceName + "]" + (list[i].HasRoot ? "R" : "")), smallFont, new SolidBrush(Color.Red), left, top);
                if(markOX){
                   graphic.DrawString(list[i].OXString, smallFont, new SolidBrush(Color.Red), left, top + 40);
                }
            }
            if(markArrows){
                graphic.DrawString("↑", bigFont, new SolidBrush(Color.Red), left + width+rams, top + 20);
            }
        }

(六)隨機修改原對象集合中的對象的值 HasRoot為false(後來添加的),標識無根。

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
    
--> 1  /// <summary>
        /// 隨機修改對象的根為 false
        /// </summary>
        private void RandomChangeItemsRootValue()
        {
            DataObjects list = manager.ItemsCollection;
            if (list == null) return;
            foreach (DataObject item in list){
                if (item.HasRoot == true){
                    item.HasRoot = Randoms.GetRandomBoolen(0.9);
                }
            }
        }

最後顯示如下(托管堆0代對象+1,1代對象+1):

同時我們應該注意到:在第6步中的方法隨機的修改了集合中對象的HasRoot屬性,再看下下一張 圖:

將上面圖對照,發現用紫色框標識的 [36]R  [39]R轉變成了[36] [39],從這裡發現從 有根 ---->無根 轉變了。這和GC中無根對象才會被回收是一個道理。

當托管堆對象2代滿了時會自動清 理0,1,2代的垃圾。有一個特殊情況,當1代中對象代+1後,轉變為2代,與原來2代的對象總共的內存超過了 容量,就有可能使應用程序中斷。(不過本人這裡也不太清楚,本人將所有的對象之空,設置為null)

最後一點:本來想用Timer來定時觸發對象的生成操作,代碼如下:

Code highlighting 

produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
    
--> 1   private void btnAutoAdd_Click(object sender, EventArgs e)
        {
             timer = new System.Timers.Timer(3000);
             timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Tick);
             timer.AutoReset = true;  
             timer.Enabled = true;    
             btnAutoAdd.Enabled = false;
             btnAutoStop.Enabled = true;
        }
        public void timer_Tick(object sender, ElapsedEventArgs e) {
             NewOneObjectOperation();
        }
        private void btnAutoStop_Click(object sender, EventArgs e)
        {
            timer.Stop();
            btnAutoAdd.Enabled = true;
            btnAutoStop.Enabled = false;
        }

但是對於Panel的refresh()操作也是線程的,這樣的話將觸發異常:

本示例的目的是用一種視覺的效果來看我們.NET平台下的垃圾收集過程,本人水平有限,難免有N 多BUG以及不太清楚的地方,還請各位多多指教。

本GC初步模擬程序代碼下載地址:Jasen.GCShow.rar [GC初步模擬效果程序 ]

http://files.cnblogs.com/jasenkin/Jasen.GCShow.rar

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved