.net垃圾回收机制编程调试试验
1. 什么是CLR GC?
它是一个基于引用跟踪和代的垃圾回收器。
从本质上,它为系统中所有活跃对象都实现了一种引用跟踪模式,如果一个对象没有任何引用指向它,那么这个对象就被认为是垃圾对象,并且可以被回收。GC通过代的概念来跟踪对象的持续时间,活跃时间段的对象被归为0代,而活跃时间更长的被归为1代和2代。CLR认为大多数对象都是短暂活跃的,因此0代被收集的频率远远高于1代和2代。
看下GC中对象及其代龄分布:
在.net中,初始分配的小对象在0代上; 通过垃圾回收后,存活的有根对象将被移动到后一代上。
有根对象(引用对象)有哪些?
1.静态、全局对象,包括缓存和进程内Session
2.Com对象计数器
3.线程堆栈上的局部变量钉扣对象,
4.本地API调用,Remoting/Webservice调用
5.finalizer 队列里的对象
先来一个简单的实例程序,
public class Student
{
public string Name { get; set; }
public string Address { get; set; }
public Student(string name, string address)
{
Name = name;
Address = address;
}
}
class Program
{
static void Main(string[] args)
{
Student wang = new Student("wang", "Beijing");
Student lee = new Student("lee", "Shanghai"); //GC.Collect();
Console.ReadLine();
}
}
Run Windbg, 看下:
!eeheap -gc
看下每一代在CLR 堆中的起始地址,输出关于GC的信息:
Number of GC Heaps: 1
generation 0 starts at 0x0000000002621030
generation 1 starts at 0x0000000002621018
generation 2 starts at 0x0000000002621000
ephemeral segment allocation context: none
segment begin allocated size
0000000002620000 0000000002621000 0000000002625fe8 0x0000000000004fe8(20456)
Large object heap starts at 0x0000000012621000
segment begin allocated size
0000000012620000 0000000012621000 0000000012627048 0x0000000000006048(24648)
Total Size 0xb030(45104)
------------------------------
GC Heap Size 0xb030(45104)
看红色字体,得知:
“第2代的起始地址是 0x0000000002621000,第0代的起始地址是 0x0000000002621030”。 这里暂时先记下,留着后面还会在看。
切换到主线程, 以便看当前程序堆栈,
~0s
关键时候到了,!clrstack -a,继续看:
:> !clrstack -a
OS Thread Id: 0x3f70 ()
>...省略无关内容... 00000000002ceef0 000007ff001701e8 System.IO.TextReader+SyncTextReader.ReadLine()
PARAMETERS:
this = 0x0000000002625a98 00000000002cef50 000007fee82dc6a2 Test.Program.Main(System.String[])
PARAMETERS:
args = 0x0000000002623598
LOCALS:
0x00000000002cef70 = 0x0000000002623658
0x00000000002cef78 = 0x0000000002623678
当程序实例化两个Student对象,执行完 Student lee = new Student("lee", "Shanghai") 后,
这里保存了2个对象,嘿嘿:
59 LOCALS:
60 0x00000000002cef70 = 0x0000000002623658
61 0x00000000002cef78 = 0x0000000002623678
看到了,第一个对象的地址是:0x0000000002623658, 而前面说,第0代的起始地址是 0x0000000002621030, 很显然,这两个对象被分配在了0代,且占用了0x20(32)个字节。
不相信,那我们来验明下正身,
0:000> .load C:\Symbols\sosex_64\sosex.dll
0:000> !gcgen 0x0000000002623658
GEN 0
呵呵,还要继续验身么,继续...
! do 0x0000000002623658,
:> !do 0x0000000002623658
Name: Test.Student
MethodTable: 000007ff00033538
EEClass: 000007ff001623a8
Size: (0x20) bytes
(D:\Test\PInvoke\CPP\Test\bin\Debug\Test.exe)
Fields:
MT Field Offset Type VT Attr Value Name
000007fee7577d90 System.String instance 00000000026235b8 <Name>k__BackingField
000007fee7577d90 System.String instance 00000000026235e0 <Address>k__BackingField
验到了, 就是Test.Student类的实例,Name和Address字段都看到了。
开个小差,看看这个实例的内容,
!do 00000000026235b8,
0:000> !do 00000000026235b8
Name: System.String
MethodTable: 000007fee7577d90
EEClass: 000007fee717e560
Size: 34(0x22) bytes
(C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: wang
Fields:
MT Field Offset Type VT Attr Value Name
000007fee757f000 4000096 8 System.Int32 1 instance 5 m_arrayLength
000007fee757f000 4000097 c System.Int32 1 instance 4 m_stringLength
000007fee75797d8 4000098 10 System.Char 1 instance 77 m_firstChar
000007fee7577d90 4000099 20 System.String 0 shared static Empty
>> Domain:Value 0000000000bcb1d0:0000000002621308 <<
000007fee7579688 400009a 28 System.Char[] 0 shared static WhitespaceChars
>> Domain:Value 0000000000bcb1d0:0000000002621a98 <<
:> !do 00000000026235e0
Name: System.String
MethodTable: 000007fee7577d90
EEClass: 000007fee717e560
Size: (0x28) bytes
(C:\Windows\assembly\GAC_64\mscorlib\2.0..0__b77a5c561934e089\mscorlib.dll)
String: Beijing
Fields:
MT Field Offset Type VT Attr Value Name
000007fee757f000 System.Int32 instance m_arrayLength
000007fee757f000 c System.Int32 instance m_stringLength
000007fee75797d8 System.Char instance m_firstChar
000007fee7577d90 System.String shared static Empty
>> Domain:Value 0000000000bcb1d0: <<
000007fee7579688 400009a System.Char[] shared static WhitespaceChars
>> Domain:Value 0000000000bcb1d0:0000000002621a98 <<
:> !do 00000000026235b8
果然, 是家住“beijing”的"wang" 同学!
通过这个事例,我们验证了,在.net中,初始分配的小对象在0代上。
那么假设,我们执行GC.Collect(),结果会怎样?
static void Main(string[] args)
{
Student wang = new Student("wang", "Beijing");
Student lee = new Student("lee", "Shanghai"); GC.Collect();
Console.ReadLine();
}
不一步步看了,给明结果吧:
000000000032eb50 000007fee82dc6a2 Test.Program.Main(System.String[])
PARAMETERS:
args = 0x00000000025d3598
LOCALS:
0x000000000032eb70 = 0x00000000025d3658
0x000000000032eb78 = 0x00000000025d3678 :> !gcgen 0x00000000025d3658
GEN
:> !gcgen 0x00000000025d3678
GEN
2 Dispose,Finalization(终结器)
Dispose:用于处置那些占用非托管资源的对象。
Finalization(终结器): 这是CLR提供的一种机制,允许对象在GC回收其内存之前执行一些清理工作。
当客户端记得的时候使用IDisposable接口释放你的非受控资源,当客户端忘记的时候防护性地使用终结器(finalizer)。它与垃圾收集器(Garbage Collector)一起工作,确保只在必要的时候该对象才受到与终结器相关的性能影响。这是处理非受控资源的一条很好的途径,因此我们应该彻底地认识它。如果你的类使用了非内存资源,它就必须含有一个终结器。你不能依赖客户端总是C#调用Dispose()方法。因为当它们忘记这样做的时候,你就面临资源泄漏的问题。没有调用Dispose是它们的问题,但是你却有过失。
用于保证非内存资源被正确地释放的唯一途径是创建终结器。
调用Dispose()方法的实现(implementation)负责下面四个事务:
1.释放所有的非受控资源。
2.释放所有的受控资源(包括未解开事件)。
3.设置标志表明该对象已经被处理过了。你必须在自己的公共方法中检查这种状态标志并抛出ObjectDisposed异常(如果某个对象被处理过之后再次被调用的话)。
4.禁止终结操作(finalization)。调用GC.SuppressFinalize(this)来完成这种事务。
Finalization(终结器)原理:
应用程序创建一个新对象时,new操作符会从堆中分配内存。如果这个对象定义了Finalize方法,那么该类型的实例在构造器被调用之前,会将指向该对象的一个指针放到一个finalization list中。finalization list是由GC控制的一个内部数据结构。列表中的每一项都指向一个对象,在回收该对象的内存前,会调用他的Finalize方法。
GC开始时,假定某些对象(如B,C,D对象)被判定位垃圾后,GC会扫描finalization list 以查找指向前述对象(如B,C,D对象)的指针。若发现finalization list有指针指向前述对象(如B,C,D对象)。finalization list会移除指向前述对象(如B,C,D对象)的指针,并把指针追加到Freachable队列。
当垃圾回收器将对象的引用从finalization list移至freachable队列时,对象不再被视为垃圾,其内存不能被回收,即对象“复活”。
然后,GC开始compact可回收内存,特殊的高优先级CLR线程专门负责清空freachable队列,并调用finalize方法。
再次GC被调用时,会发现应用程序的根不再指向它,freachable队列也已经清空。所以,这些对象的内存会被直接回收。
整个过程中,实现Finalization(终结器)的对象需要至少执行两次垃圾回收才能释放其所占内存。(假设对象代龄被提升,则可能多次GC才回收其内存)。
规范的Dispose实现模式:
public class ComplexCleanupBase : IDisposable
{
// some fields that require cleanup
private SafeHandle handle; private bool disposed = false; // to detect redundant calls public ComplexCleanupBase()
{
// allocate resources
} protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
if (handle != null)
handle.Dispose();
// dispose-only, i.e. non-finalizable logic
} // shared cleanup logic
disposed = true;
}
} public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
} ~ComplexCleanupBase()
{
Dispose(false);
}
}
了解上述后,来看个问题:
a. 数据库连接,文件连接假如不手动dispose(),资源会被回收么?
回答上面这个问题前,先做个试验,
protected void Button1_Click(object sender, EventArgs e)
{
byte[] b = new byte[] { , , , , };
FileStream fs = new FileStream(@"d:\temp.dat", FileMode.Create);
fs.Write(b, , b.Length); } protected void Button2_Click(object sender, EventArgs e)
{
GC.Collect();
} protected void Button3_Click(object sender, EventArgs e)
{
File.Delete(@"d:\temp.dat");
}
Button1,Button2,Button3来回点几下,看看会有什么现象?
连续点击Button1或先点Button1后点Buttion3,第二次都会报错,说明文件连接没有被释放。但是如果点击Button1再点击Button2再点击Button1那么就不会报错了,因为Button2垃圾回收将文件连接关闭了。
这么说来,数据库连接,文件连接假如不手动dispose(),资源也会被GC回收,因为FileStream里的SaveFileHandle实现了Finalize,执行GC,其Finalize方法起了作用。
好强大的GC哦! 只不过,需要等待GC.collect()才能释放这些资源连接,但是呢这些资源开着开销很大很昂贵,所以推荐用完即手动dispose().
这样看规范的Dispose实现模式,能够确保.net资源即使被遗忘关闭,借助垃圾回收机制,也能顺利清理其资源。
好了,有了上面这些基础之后,再来看一个例子:
public class Foo
{
Timer _timer; public Foo()
{
_timer = new Timer();
_timer.Elapsed += _timer_Elapsed;
_timer.Start();
} void _timer_Elapsed(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Tick");
}
}
class Program
{
static void Main(string[] args)
{
Foo foo = new Foo();
foo = null;
Console.ReadLine();
}
}
注:Timer类来自using System.Timers;
执行发现会出现一连串的 “Tick”。 这里foo=null并未起到作用,Timer资源并未关闭。这里我们不深究为何foo=null不起作用,实际上是因为.net编译做了优化处理,foo=null直接被编译器忽视了。
那怎么办才能做到万事顺利地关闭Timer?
Dispose模式登场,修改后的类如下:
public class Foo : IDisposable
{
Timer _timer; public Foo()
{
_timer = new Timer();
_timer.Elapsed += _timer_Elapsed;
_timer.Start();
} void _timer_Elapsed(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Tick");
} public void Dispose()
{
_timer.Dispose();
}
~Foo()
{
Dispose();
}
}
class Program
{
static void Main(string[] args)
{
using (Foo foo = new Foo())
{
System.Threading.Thread.Sleep();
}
Console.ReadLine();
}
}
.net垃圾回收机制编程调试试验的更多相关文章
- JavaScript具有自动垃圾回收机制
JavaScript具有自动垃圾回收机制 原理: 找出那些不再继续使用的变量,然后释放其占用的内存. 正常的生命周期: 局部变量指在函数执行的过程中存在.而在这个过程中,会为局部变量在栈或 ...
- java垃圾回收机制
1 .垃圾回收机制(GC)垃圾回收就是回收内存中不再使用对象:(1)垃圾回收的步骤:1)查找内存中不再使用的对象:2)释放这些对象所占用的内存:(2)查找内存中不再使用的对象方法:1)引用计数法如果一 ...
- 垃圾回收机制GC知识再总结兼谈如何用好GC
一.为什么需要GC 应用程序对资源操作,通常简单分为以下几个步骤: 1.为对应的资源分配内存 2.初始化内存 3.使用资源 4.清理资源 5.释放内存 应用程序对资源(内存使用)管理的方式,常见的一般 ...
- 【转载】Java垃圾回收机制
原文地址:http://www.importnew.com/19085.html Java垃圾回收机制 说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联 ...
- 【转】深入理解 Java 垃圾回收机制
深入理解 Java 垃圾回收机制 一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再 ...
- JAVA的垃圾回收机制
1. 垃圾回收的意义 在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象:而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾.JVM的 ...
- Python的垃圾回收机制
Python的GC模块主要运用了“引用计数”(reference counting)来跟踪和回收垃圾.在引用计数的基础上,还可以通过“标记-清除”(mark and sweep)解决容器对象可能产生的 ...
- python垃圾回收机制的一些理解
概览: 主要通过 引用计数来进行垃圾收集, 就是说,当一个对象没有被其他对象引用的时候,会释放掉内存. 但是会有一些循环引用的对象,通过上面的方法,是没有办法清除掉的.所以,pyt ...
- 闭包内的微观世界和js垃圾回收机制
一.什么是闭包? 官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.相信很少有人能直接看懂这句话,因为他描述的太学术.其实这句话 ...
随机推荐
- 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新
本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...
- IE6、7下html标签间存在空白符,导致渲染后占用多余空白位置的原因及解决方法
直接上图:原因:该div包含的内容是靠后台进行print操作,输出的.如果没有输出任何内容,浏览器会默认给该空白区域添加空白符.在IE6.7下,浏览器解析渲染时,会认为空白符也是占位置的,默认其具有字 ...
- .NET Core全新路线图
.NET Core / ASP.NET Core 1 RTM发布两周后,社区也很积极,收到了非常多的反馈,上周五微软的scott Hunter 在dotnet团队官方博客上发布了.NET Core全新 ...
- java Web项目创建之一(普通java web项目的创建与发布)
1.创建新的web项目 file->new_>Dynamic Web Project(如图) 或file->new->Project->Web->Dynamic W ...
- HTML5 progress和meter控件
在HTML5中,新增了progress和meter控件.progress控件为进度条控件,可表示任务的进度,如Windows系统中软件的安装.文件的复制等场景的进度.meter控件为计量条控件,表示某 ...
- UWP开发之Template10实践二:拍照功能你合理使用了吗?(TempState临时目录问题)
最近在忙Asp.Net MVC开发一直没空更新UWP这块,不过有时间的话还是需要将自己的经验和大家分享下,以求共同进步. 在上章[UWP开发之Template10实践:本地文件与照相机文件操作的MVV ...
- Oracle 数据库知识汇总篇
Oracle 数据库知识汇总篇(更新中..) 1.安装部署篇 2.管理维护篇 3.数据迁移篇 4.故障处理篇 5.性能调优篇 6.SQL PL/SQL篇 7.考试认证篇 8.原理体系篇 9.架构设计篇 ...
- Spring的数据库开发
Spring JDBC框架操作mysql数据库 Spring中的JDBC为我们省去连接和关闭数据库的代码,我们着重关注对数据库的操作.Sprin ...
- 【C#公共帮助类】 ToolsHelper帮助类
这个帮助类,目前我们只用到了两个,我就先更新这两个,后面有用到的,我会继续更新这个Helper帮助类 在Tools.cs中 有很多方法 跟Utils里是重复的,而且Utils里的方法更加新一点,大家可 ...
- Android菜单项内容大全
一.介绍: 菜单是许多应用中常见的用户界面组件. Android3.0版本以前,Android设备会提供一个专用"菜单"按钮呈现常用的一些用户操作, Android3.0版本以后, ...