在上一篇中,讨论了字符串常量的拘留池和不可变性;对于字符串变量,没有这个特性(或其他DotNet的非托管资源),当我们使用完后就要手动回收,即将变量的值指向null(p=null),然而堆内存中,那个没有任何变量引用的对象并没有立即回收(还占用一定量的堆内存),所以当我们要进行一个相当耗时且最好不要中断的操作时,最好调用垃圾回收,回收内存中的“垃圾”(没有变量引用的对象和非托管资源)以保证内存足够使用,这里提一下,所谓非托管资源,指的是非Dotnet开辟的资源,用完后再调用释放资源,如:数据库连接中的SqlConnection,SqlCommand等对象,用完要调用Dispose()释放,通常使用这类对象用using(),保证离开using范围,对象被回收。
接下来,说一下垃圾回收机制(垃圾回收机制只回收托管堆中的内存资源),在DotNet中有“代”的概念(共3代),举例说明:
同时,我们假设第0代大小为256M,第1代是512M,第2代是1G。程序执行一段时间后,定义了3个变量d、e、f,第1代中b指向null,但是第0代放不下,那么Dotnet会提升第0代中存活变量的代,结果如下:
程序继续执行一段时间,变量C又指向null,同时定义3个变量g,h,i,DotNet看到第0代放不下,继续提升第0代变量的代同时将新变量放入第1代
依次类推,直至3代的内存消耗殆尽,此时DotNet会提升3个代的存储空间,保证变量能放进去。直至提升代的空间后,仍放不下,此时就内存溢出,抛异常了。当我们开始就定义一个大对象---1G的对象),将被直接放入第2代。
了解了代的概念,开始上代码:
1 Person p = new Person() { ID = 1, Name = "张三", BirthDate = DateTime.Now }; //对象初始化器 2 p = null; 3 GC.Collect(); 4 Console.WriteLine(p.ID+"=="+p.Name+"=="+p.BirthDate);
GC.Collect()就是告诉DotNet,请垃圾回收一把。这时,DotNet会将程序暂停,保存变量的当前状态,进行垃圾回收,回收完毕,重新分配内存,让程序继续执行(因为程序会暂停执行,所以对性能会有影响,所以一般不建议经常调用GC.Collect()来执行垃圾回收),上面的代码会抛空引用异常。
弱引用,指的是一个变量可以被垃圾回收了。这个对象不会用了,但是日后又担心会用,而且这个对象的创建非常耗时,我们就要用弱引用引用起来,需要注意的是,弱引用引用起来的对象是可以被垃圾回收的,但只要没被垃圾回收(能引用得到),就可以继续使用。
1 Person p = new Person() { ID = 1, Name = "张三", BirthDate = DateTime.Now }; //对象初始化器 2 3 WeakReference wReference = new WeakReference(p); //将p对象弱引用起来 4 p = null; 5 6 GC.Collect(); //手动调用垃圾回收 7 object o = wReference.Target; 8 if (o!=null&&wReference.IsAlive) 9 { 10 Person p1 = o as Person; 11 Console.WriteLine(p1.ID + "==" + p1.Name + "==" + p1.BirthDate); 12 } 13 else 14 { 15 Console.WriteLine("对象被垃圾回收了,请重新创建对象吧"); 16 }
运行可知。这是因为我们手动调用了GC.Collect(),把这句话注释掉,再运行,
Person p = new Person() { ID = 1, Name = "张三", BirthDate = DateTime.Now }; //对象初始化器 WeakReference wReference = new WeakReference(p); //将p对象弱引用起来 p = null; //GC.Collect(); //手动调用垃圾回收 object o = wReference.Target; if (o!=null&&wReference.IsAlive) { Person p1 = o as Person; Console.WriteLine(p1.ID + "==" + p1.Name + "==" + p1.BirthDate); } else { Console.WriteLine("对象被垃圾回收了,请重新创建对象吧"); }
运行结果如下:
由此可见,虽然将p指向了null,但是因为还没被回收,我们通过弱引用(WeakReference)能找到它,便可继续使用。
在使用弱引用时,要注意:
一定是先object o = wReference.Target; 将对象强引用起来,再加判断,如果先判断wReference.Target != null,再执行object o = wReference.Target时正巧p被垃圾回收了,那么o就是null。
弱引用的适用场景:
1.创建一个对象非常耗时;
2.这个对象不会用了(可以被垃圾回收),但是日后又担心会用;
3.弱引用的对象也不是一定就存在,如果手动调用GC.Collect()或者正好被垃圾回收,也会引用不到,此时若要用,只能重新创建对象。