• CLR GC垃圾收集过程模拟(用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()方法被调用:

     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()执行(释放资源)。

     

    代码

         

    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(后来添加的),标识无根。

    (一) 准备工作

    先自创建一个类,主要是以该对象来作为操作的。

     public class DataObject : Jasen.GCShow.IDataObject
        {
            
    public Boolean HasFinalizeMethod { getset; }
            
    public Boolean HasRoot { getset; }
            
    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 { getset; }
            
    public String NiceName { getset; }
            
    public Int32 Generation { getset; }
        }
    然后就是该类对象集合,实现遍历以及索引:

    其次就是该对象集合的管理类,负责所有对象的ItemCollection,以及0,1,2代对象集合,以及终结链表,终结可达队列

     1  public class DataObjectManager : Jasen.GCShow.IDataObjectManager 
     2     {
     3         DataObjects items = new DataObjects();
     4         Queue<DataObject> freachableQueue = new Queue<DataObject>();
     5         DataObjects finalizeTable = new DataObjects();
     6 
     7         public DataObjects ItemsCollection 
     8         {
     9             get { return items; }
    10             set { items = value; }
    11         }
    12         public DataObjects ZeroGenerationCollection
    13         {
    14             get { return GetCollection(0); }
    15         }
    16 
    17         public DataObjects GetCollection(int generation) 
    18         {
    19            if (ItemsCollection.Count() == 0return null;
    20            DataObjects generationObjects = new DataObjects();
    21            foreach(DataObject obj in ItemsCollection){
    22               if(obj.Generation==generation){
    23                   generationObjects.Add(obj);
    24               }
    25            }
    26             return generationObjects;
    27         }
    28         public DataObjects OneGenerationCollection
    29         {
    30             get { return GetCollection(1); }
    31         }
    32         public DataObjects TwoGenerationCollection 
    33         {
    34             get { return GetCollection(2); }
    35         }
    36         public DataObjects FinalizeTable  
    37         {
    38             get { return finalizeTable; }
    39             set { finalizeTable = value; }
    40         }
    41         public Queue<DataObject> FreachableQueue 
    42         {
    43             get { return freachableQueue; }
    44             set { freachableQueue = value; }  
    45         }  
    46     }

     

    (二)初始化一个属性值为随机值的 DataObject对象

    通过随机设置类的值来实例化一个对象

    DataObject item = new DataObject()
                {
                    HasFinalizeMethod 
    = Randoms.GetRandomBoolen(0.3),
                    HasRoot 
    = Randoms.GetRandomBoolen(0.6),
                    Color 
    = Randoms.GetRandomColor(),
                    Number 
    = Randoms.RandomNum(13),
                    Name 
    = Guid.NewGuid().ToString(),
                    NiceName 
    = Randoms.AddNum().ToString(),
                    Generation 
    = 0 // 默认为0代
                };

    以上的值大部分是随机的,不确定的,比如下面的方法----->返回随机比例为rate(比如0.3)的true值,它等价于有30%的概率返回true,70%概率返回false,

     /// <summary>
            ///
     返回随机比例为rate的 true值
            ///
     </summary>
            ///
     <param name="rate"></param>
            ///
     <returns></returns>
            
    public static Boolean GetRandomBoolen(double rate) {
                
    if (rate < 0 || rate > 1throw 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;
            }

     随机颜色如下

     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代内存是否充足

    判断的大概过程如下:

     #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代对象的主要操作如下:

      private void GCSystemOperation()
            {
                ClearFreachableQueue();
                DataObjects temps 
    = new DataObjects();

                
    //清理没根的没终结方法的0代对象  0代对象 +1 (清除)
                DataObjects list = manager.ZeroGenerationCollection;
                
    if (list == nullreturn;
                
    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次收集就会将可达队列中的指针清理)

    执行下列代码:

     1  private void ClearFreachableQueue()
     2         {
     3             //清理终结可达队列中的对象 没根 有终结方法 (清除)   一般为清理上次收集数据
     4             while (manager.FreachableQueue.Count > 0){
     5                 DataObject obj = manager.FreachableQueue.Dequeue();
     6                 manager.ItemsCollection.Remove(obj);
     7             }
     8             MessageBox.Show("清理可达队列对象");
     9             //终结链表中的数据  --》可达队列 
    10             foreach (DataObject item in manager.FinalizeTable){
    11                 if (item.HasRoot == false){
    12                     manager.FreachableQueue.Enqueue(item);
    13                 }
    14             }
    15             MessageBox.Show("将终结链表中的可达对象移动至可达队列");
    16             foreach (DataObject obj in manager.FreachableQueue){
    17                 manager.FinalizeTable.Remove(obj);
    18             }
    19             MessageBox.Show("移除终结链表中包含的可达队列对象");
    20         }

    显然,将终结链表的数据移动到可达队列后,然后再移除终结链表包含的可达队列的指针,操作后如下:

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

     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代对象的主要操作如下:

     1  private void HandleTwoGeneration(DataObjects currentTempObjects)
     2         {
     3             Boolean enough = CheckTwoGenerationEnough(currentTempObjects);
     4             if (enough){
     5                 GenerationAddOne(currentTempObjects);
     6             }
     7             else {
     8                 MessageBox.Show("托管堆2代对象内存满了,清理托管堆2代无根对象");
     9                 ClearGenerationUnusefulObject(manager.TwoGenerationCollection);
    10                 if (CheckGenerationEnough(currentTempObjects, manager.TwoGenerationCollection, SystemConst.TwoGenerationLength)){
    11                     MessageBox.Show("托管堆1代对象  对象代+1");
    12                     GenerationAddOne(currentTempObjects);
    13                 }
    14                 else
    15                     ClearToEmpty(); //托管堆对象全部清理
    16                 }
    17             }   
    18         }

    (五)垃圾收集后内容的显示,调用面板panel的refresh()方法。

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

      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)

     1   private void FillRectangle(Graphics graphic, DataObjects list,Boolean markArrows,Boolean markOX) 
     2         {
     3             float left = 0,width = 0,top = 0,height = 20,rams=-15;          
     4             for (int i = 0; i < list.Count(); i++){
     5                 int sum = 0;
     6                 if (i != 0) {
     7                     for (int z = 0; z < i; z++){
     8                         sum += list[z].Number;//i-1次
     9                     }
    10                 }
    11                 left = sum * SystemConst.GraphicLength;
    12                 width = SystemConst.GraphicLength * list[i].Number;              
    13                 graphic.FillRectangle(new SolidBrush(list[i].Color), left, top, width, height);
    14                 graphic.FillRectangle(new SolidBrush(Color.Red), left, top, 2, height);             
    15                 graphic.DrawString(("[" + list[i].NiceName + "]" + (list[i].HasRoot ? "R" : "")), smallFont, new SolidBrush(Color.Red), left, top);
    16                 if(markOX){
    17                    graphic.DrawString(list[i].OXString, smallFont, new SolidBrush(Color.Red), left, top + 40);
    18                 }
    19             }
    20             if(markArrows){
    21                 graphic.DrawString("", bigFont, new SolidBrush(Color.Red), left + width+rams, top + 20);
    22             }
    23         }

    (六)随机修改原对象集合中的对象的值 HasRoot为false(后来添加的),标识无根。

     1  /// <summary>
     2         /// 随机修改对象的根为 false
     3         /// </summary>
     4         private void RandomChangeItemsRootValue()
     5         {
     6             DataObjects list = manager.ItemsCollection;
     7             if (list == nullreturn;
     8             foreach (DataObject item in list){
     9                 if (item.HasRoot == true){
    10                     item.HasRoot = Randoms.GetRandomBoolen(0.9);
    11                 }
    12             }
    13         }

    最后显示如下(托管堆0代对象+1,1代对象+1):

    同时我们应该注意到:在第6步中的方法随机的修改了集合中对象的HasRoot属性,再看下下一张图:

    将上面图对照,发现用紫色框标识的 [36]R  [39]R转变成了[36] [39],从这里发现从 有根 ---->无根 转变了。这和GC中无根对象才会被回收是一个道理。

    当托管堆对象2代满了时会自动清理0,1,2代的垃圾。有一个特殊情况,当1代中对象代+1后,转变为2代,与原来2代的对象总共的内存超过了容量,就有可能使应用程序中断。(不过本人这里也不太清楚,本人将所有的对象之空,设置为null)

    最后一点:本来想用Timer来定时触发对象的生成操作,代码如下:

     1   private void btnAutoAdd_Click(object sender, EventArgs e)
     2         {
     3              timer = new System.Timers.Timer(3000);
     4              timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Tick);
     5              timer.AutoReset = true;  
     6              timer.Enabled = true;    
     7              btnAutoAdd.Enabled = false;
     8              btnAutoStop.Enabled = true;
     9         }
    10         public void timer_Tick(object sender, ElapsedEventArgs e) {
    11              NewOneObjectOperation();
    12         }
    13         private void btnAutoStop_Click(object sender, EventArgs e)
    14         {
    15             timer.Stop();
    16             btnAutoAdd.Enabled = true;
    17             btnAutoStop.Enabled = false;
    18         }

    但是对于Panel的refresh()操作也是线程的,这样的话将触发异常:

    本示例的目的是用一种视觉的效果来看我们.NET平台下的垃圾收集过程,本人水平有限,难免有N多BUG以及不太清楚的地方,还请各位多多指教。

    本GC初步模拟程序代码下载地址:Jasen.GCShow.rar [GC初步模拟效果程序 ]

    http://www.cnblogs.com/jasenkin/archive/2010/10/03/clr_gc_show.html

  • 相关阅读:
    2017.1.16【初中部 】普及组模拟赛C组总结
    用Redis实现分布式锁 与 实现任务队列
    Mysql+Keepalived双主热备高可用操作记录
    Linux下防御DDOS攻击的操作梳理
    真正的ddos防御之道,简单干脆有效!
    ip黑白名单防火墙frdev的原理与实现
    一种简单的处理大流量访问的方法
    PHP解决网站大流量与高并发
    PHP反射机制实现自动依赖注入
    nginx 根据域名和地址跳转
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2059326.html
Copyright © 2020-2023  润新知