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 { 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 ; }
}
其次就是该 对象集合的管理类 ,负责所有对象的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初步模拟效果程序 ]
http://www.cnblogs.com/jasenkin/archive/2010/10/03/clr_gc_show.html
作者: Leo_wl
出处: http://www.cnblogs.com/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息查看更多关于CLR GC垃圾收集过程模拟(用C#来显示垃圾收集过程的视觉效果)的详细内容...