发文章
发文工具
撰写
网文摘手
文档
视频
思维导图
随笔
相册
原创同步助手
其他工具
图片转文字
文件清理
AI助手
留言交流
其实吧,内存泄露一直是个令人头疼的问题,在带有GC的语言中这个情况得到了很大的好转,但是仍然可能会有问题。
内存泄露不是指内存坏了,也不是指内存没插稳漏出来了,简单来说,内存泄露就是在你期待的时间内你程序所占用的内存没有按照你想象中的那样被释放。
因此什么是你期待的时间呢?明白这点很重要。如果一个对象占用内存的时间和包含这个对象的程序一样长,但是你并不期望是这样。那么就可以认为是内存泄露了。用具体例子来说明如下:
class Button { public void OnClick(object sender, EventArgs e) { ... } } class Program { static event EventHandler ButtonClick; static void Main(string[] args) { Button button = new Button(); ButtonClick += button.OnClick; } }
上面这段代码中,我们使用了一个静态的事件,而静态成员的生命周期是从AppDomain被加载开始,直到AppDomain被卸载,也就是说在通常情况下如果进程没被关闭,又忘记取消注册事件,那么ButtonClick事件包含的EventHandler委托所引用的对象会一直存在到进程结束为止,这就造成了内存泄露问题。这也是.NET中最常见的内存泄露问题的原因之一。后面我会接着说怎么解决这种事件造成的泄露问题。
1、引用计数
引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个 变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办 法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。
像原来IE6中Javascript中原生对象内存回收的方式就是通过检查对象是否有引用来判断一个对象是否是垃圾。IE9之前,其BOM和DOM中的对象是使用C++以COM对象的形式实现的,而COM对象的垃圾收集机制采用的也是引用计数策略。而这种方式通常会因为循环引用导致内存泄露,也就是A引用B的同时,B也引用者A。 在Objective-C中也会有这样的循环引用的问题。在Objective-C中的解决方案就是给一方标记为weak,介绍可以参看这里,关于Objective-C中的委托模式的介绍。
2、标记清除法(mark-weep)
C#中采用的是标记法回收内存,全部对象都要标记,并且只标记一次就不再标记。判断一个对象是不是垃圾取决于是否有引用,而是取决是是否被root引用。
root的类型有寄存器中的变量,线程栈上的变量,静态变量等。
我们来看一幅通常情况下的对象图,图中有一个循环引用。
我们抽取其中一部分图说明
在采用标记清除策略的实现中,由于函数执行之后,local3出栈,离开了作用域,因此这种相互引用在标记清除法中不是个问题。
我们很容易看出,因为每一个对象都要mark,因此创建大量的小对象会给Mark阶段造成压力。值得注意的是,在GC的mark和weep阶段,会挂起所有线程,因此创建大量的线程也是会对GC造成问题。这个问题我以后会再讨论。
如前面所说,忘记取消注册事件通常是.NET中最常见的内存泄露问题,我们怎么自动化的解决这个问题呢?也就是说当方法所属的对象已经被标记为垃圾的时候,我们就在事件中取消注册这个方法。这时就可以通过弱引用来实现。
委托的本质就是一个类,包含了几个关键属性:
1. 指向原对象的Target属性(强引用)。
2. 一个指向方法的ptr指针。
3. 内部维护着一个集合(delegate是以链表结构实现)。
因为.NET中的委托是强引用,我们要把它改成弱引用,我们可以抓住这个这些特征,创建一个自己的WeakDelegate类。
事件的本质就是一个访问器方法,和委托的关系类似于字段和属性,也就是控制外部对字段的访问。我们可以通过自定义add和remove方法来把外部的委托转换成我们自己定义的委托。
public class Button { private class WeakDelegate { public WeakReference Target; public MethodInfo Method; } private List<WeakDelegate> clickSubscribers = new List<WeakDelegate>(); public event EventHandler Click { add { clickSubscribers.Add(new WeakDelegate { Target = new WeakReference(value.Target), Method = value.Method }); } remove { ..... } } public void FireClick() { List<WeakDelegate> toRemove = new List<WeakDelegate>(); foreach (WeakDelegate subscriber in clickSubscribers) { //第一个Target表示方法所属的对象,第二个Target表示这个对象是否被标记为垃圾,如果为null则表示为已经被标记为垃圾。 object target = subscriber.Target.Target; if (target == null) { toRemove.Add(subscriber); } else { subscriber.Method.Invoke(target, new object[] { this, EventArgs.Empty }); } } clickSubscribers.RemoveAll(toRemove); } }
弱引用还可以用来创建一个对象池,对象池就是通过管理少量的对象来减少内存和GC压力。我们可以通过强引用来表示对象池内最小的对象数量,通过弱引用来表示可以达到的最大的数量。
来自: 昵称10504424 > 《C#》
0条评论
发表
请遵守用户 评论公约
.Net内存分配笔记
引用类型存贮在动态的堆中, 堆是由应用程序控制的可随时申请和释放该空间,在.Net中一般情况下有垃圾收集器处理.如果引用类型嵌套在值类型里的话, 值类型在线程的堆栈上, 而引用类型在GC堆上, 只是对引...
C#技术漫谈之垃圾回收机制(GC)
如果Gen 0 heap内存达到阀值,则触发0代GC,0代GC后Gen 0中幸存的对象进入Gen1。如果Gen 1的内存达到阀值,则进行1代GC,1代GC将Gen 0 h...
深入了解C#系列:谈谈C#中垃圾回收与内存管理机制 - 学IT网 xueit.com
深入了解C#系列:谈谈C#中垃圾回收与内存管理机制 - 学IT网 xueit.comhttp://www.xueit.com/html/2009-02/2_624_00.html 今天抽空来讨论一下.Net的垃圾回收与内存管理机制,也算是完成上个《WCF分布式...
.NET基础拾遗(1)类型语法基础和内存管理基础
①当每个包含Finalize方法的类型的实例对象被分配时,.NET会在一张特定的表结构中添加一个引用并且指向这个实例对象,暂且称该表为“带...
Java面试题常见问题
要请求垃圾收集,可以调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() 。答:以Stateful Session Bean 为例:其Cache 大小决定了内存中可以同时存在的Bean 实例的数量,根据MRU 或NRU 算...
Android彻底组件化方案实践方法!算法太TM重要了
Java 多线程之间是通过共享内存来通信的,每个线程都有自己的本地内存共享变量存放于主内存中,线程会拷贝一份共享变量到本地内存volatile 关键字就是给内存模型服务的,用来保证内存可见性和顺序性JVM...
静态方法
浅谈Java中的System.gc()的工作原理
浅谈Java中的System.gc()的工作原理趁着今天写程序的热乎劲儿,小谈一下System.gc()这个神秘的东东~Java中的内存分配是随着new一个新的对象来实现的,这个很简单,而且也还是有一些可以“改进”内存回...
【读者投稿】浅谈垃圾回收
如果内存已经被占满了,那么就需要垃圾回收器收集扫描整个内存区域,并且为了整理内存,可能带来更大的复制操作或者其他问题,因此对内...
微信扫码,在手机上查看选中内容