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

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

 

(一) 准备工作

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

 

  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 ; }
    }

 

然后就是该 类对象集合,实现遍历以及索引:
ExpandedBlockStart.gif
   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代对象集合,以及终结链表,终结可达队列

 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()  ==   0 return   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( 1 3),
                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  >   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;
        }

 

 随机颜色如下

  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  ==   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次收集就会将可达队列中的指针清理)

执行下列代码:

 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  ==   null return ;
 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初步模拟效果程序 ]

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值