cir from c# 托管堆和垃圾回收

1,托管堆基础

    1. 调用IL的newobj 为资源分配内存
    2. 初始化内存,设置其初始状态并使资源可用.类型的实列构造器负责设置初始化状态
    3. 访问类型的成员来使用资源
    4. 摧毁状态进行清理
    5. 释放内存//垃圾回收期负责.

2,从托管堆创造对象

  1. 进程初始化时候,CLR划出一个地址空间区域作为托管堆,并且初始化NextObjPtr指针,其指向下一个可用的托管堆地址.
  2. c#new操作 首先计算类型的字段所需要的字节数
  3. 加上对象开销的字节数:类型对象指针和同步块索引.64位机器上是16字节,32位机器是8字节.
  4. CLR检查是否空间够,如果够,则在指向地址处放入对象,并且将对象指针和同步块索引清0.接着,调用构造器.
  5. image

3,垃圾回收算法

我们先来看垃圾回收的算法与主要流程:
算法:引用跟踪算法。因为只有引用类型的变量才能引用堆上的对象,所以该算法只关心引用类型的变量,我们将所有引用类型的变量称为
主要流程:
1.首先,CLR暂停进程中的所有线程。防止线程在CLR检查期间访问对象并更改其状态。
2.然后,CLR进入GC的标记阶段。
 a. CLR遍历堆中的对象(实际上是某些代的对象,这里可以先认为是所有对象),将同步块索引字段中的一位设为0,表示对象是不可达的,要被删除。
 b. CLR遍历所有,将所引用对象的同步块索引位设为1,表示对象是可达的,要保留。
3.接着,CLR进入GC的碎片整理阶段。
 a. 将可达对象压缩到连续的内存空间(大对象堆的对象不会被压缩)
 b. 重新计算所引用对象的地址。
4.最后,NextObjPtr指针指向最后一个可达对象之后的位置,恢复应用程序的所有线程。

image

重要提示:静态字段引用对象会一直存在.内存泄漏的常见原因是静态字段引用某个集合对象,然后不停添加数据项.因此,尽量避免使用静态变量.

4,垃圾回收和调试

 public class GCRef
    {
        private static void TimerCallback(Object o)
        {
            Console.WriteLine("In TimerCallBack:" + DateTime.Now);
            GC.Collect();
        }
        public static void Go()
        {
            Timer t = new Timer(TimerCallback, null, 0, 2000);
            Console.ReadKey();
            t.ToString();//使用该对象也可以使对象存活
            t.Dispose();//使用显式垃圾回收的方式,进行垃圾回收.
        }
    }

由于进行垃圾回收,所以,只会回调一次该方法.当后面再使用方法的时候,才能够一致保存对象t.

5,垃圾回收的代的概念

  • 对象越新,生存期越短
  • 对象越老,生存期越长
  • 回收堆的一部分,速度快于回收整个堆
  • GC一个有3代0代,1代,2代

6,代的生成机制

  • 初始化后,所有添加到堆中的对象都是0代.当0代对象操作某个预算容量的时候,进行垃圾回收.回收后剩下的,就是1代对象
  • 重复上面的过程,然后1代对象不断的增加,知道其值超过某个预算容量,然后进行1代和0代的垃圾回收.1代剩余的到2代中;
  • 1代剩余的对象到2代中,0代剩余的到1代中.0代清空.

7,GC.ReRegisterForFinalize(Object) 方法

 public static class GCNotification
    {
        private static Action<int> s_gcDone = null;

        public static event Action<int> GCDone
        {
            add
            {
                if (s_gcDone == null) { new GenObject(0);new GenObject(2); }
                s_gcDone += value;
            }
            remove
            {
                s_gcDone -= value;
            }
        }
        private sealed class GenObject
        {
            private int m_generation;
            public GenObject(int generation) { m_generation = generation; }
            ~GenObject()
            {
                if (GC.GetGeneration(this) >= m_generation)
                {
                    Action<int> temp = Volatile.Read(ref s_gcDone);
                    if (temp != null) temp(m_generation);
                }

                if ((s_gcDone != null) && (!AppDomain.CurrentDomain.IsFinalizingForUnload()) && (!Environment.HasShutdownStarted))
                {
                    if (m_generation == 0) new GenObject(0);
                    else GC.ReRegisterForFinalize(this);
                }
                else { } //让对象被回收}
            }

        }
        public static void Go()
        {
            GCDone += x => Console.WriteLine($"GenObject {x}");
            GC.Collect();

        }

思路,1,自定义了事件,并且世界的通知对象类型是Action<int>,并且手动进行了触发.在垃圾回收的代>设定代m_generation时.

      

8,Finalization Queue和Freachable Queue-ReRegisterFinalize()和SupressFinalize()函数

一个将对象指针从Finalization队列中去除,一个重新加入到Finalization队列中.

这两个队列和.net对象所提供的Finalize方法有关。这两个队列并不用于存储真正的对象,而是存储一组指向对象的指针。当程序中使用了new操作符在Managed Heap上分配空间时,GC会对其进行分析,如果该对象含有Finalize方法则在Finalization Queue中添加一个指向该对象的指针。在GC被启动以后,经过Mark阶段分辨出哪些是垃圾。再在垃圾中搜索,如果发现垃圾中有被Finalization Queue中的指针所指向的对象,则将这个对象从垃圾中分离出来,并将指向它的指针移动到Freachable Queue中。这个过程被称为是对象的复生(Resurrection),本来死去的对象就这样被救活了。为什么要救活它呢?因为这个对象的Finalize方法还没有被执行,所以不能让它死去。Freachable Queue平时不做什么事,但是一旦里面被添加了指针之后,它就会去触发所指对象的Finalize方法执行,之后将这个指针从队列中剔除,这是对象就可以安静的死去了。.net framework的System.GC类提供了控制Finalize的两个方法,ReRegisterForFinalize和SuppressFinalize。前者是请求系统完成对象的Finalize方法,后者是请求系统不要完成对象的Finalize方法。ReRegisterForFinalize方法其实就是将指向对象的指针重新添加到Finalization Queue中。这就出现了一个很有趣的现象,因为在Finalization Queue中的对象可以复生,如果在对象的Finalize方法中调用ReRegisterForFinalize方法,这样就形成了一个在堆上永远不会死去的对象,像凤凰涅槃一样每次死的时候都可以复生。

public class MyFinalizeObject1//新建一个带终结器的会死灰复燃的类
    {
        public static int CountFinalized = 0;//类终结次数
        public string Name { get; }//类名
        public MyFinalizeObject1(string name)//类构造器
        { Name = name; }
        public static void Go()
        {
            new MyFinalizeObject1("abc");
            Timer t = new Timer(x => GC.Collect(), null, 0, 2000);
            Console.ReadKey();
            t.Dispose();
        }
        ~MyFinalizeObject1()//将在Finalization Queue队列中的类首先放入 Freachable Queue中,然后调用终结器,然后再释放.
        {
            Console.WriteLine(this.GetType().ToString() + $" {this.Name} has been DC {CountFinalized++}");//类每次终结输出信息
            GC.ReRegisterForFinalize(this);//将类重新放入终结队列. 
        }
    }

在GO中创建一个Threading.TImer对象,让其每2秒实行一次垃圾显示回收

ClrFromCSharp_2_2.LearnGC.MyFinalizeObject1 abc has been DC 0
ClrFromCSharp_2_2.LearnGC.MyFinalizeObject1 abc has been DC 1
ClrFromCSharp_2_2.LearnGC.MyFinalizeObject1 abc has been DC 2

9,垃圾回收模式:

  • 工作站:针对客户端优化,GC造成延迟很低
  • 服务器:托管堆被分配到每个CPU一个GC,每个GC负责各自区域,并且在每个CPU上面运行特殊的线程:并发回收垃圾.
  • 配置文件告诉使用服务器回收器
    • <?xml version="1.0" encoding="utf-8"?>
      <configuration>
        <runtime>
          <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
            <probing privatePath="AuxFiles"/>//在哪个位置寻找程序集.
          </assemblyBinding>
          <gcServer enabled="true"/>
        </runtime>
          <startup>
              <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
          </startup>
      </configuration>
      
      
  •    使用GC.IsServerGC 来查询是否是这个模式.结果是True---上面配置了.
    public class MyFinalizeObject1//新建一个带终结器的会死灰复燃的类
        {
            public static int CountFinalized = 0;//类终结次数
            public string Name { get; }//类名
            public MyFinalizeObject1(string name)//类构造器
            { Name = name; }
            public static void Go()
            {
                new MyFinalizeObject1("abc");
                Console.WriteLine($"{GCSettings.IsServerGC}");//查看是否启用了服务器垃圾回收
                Timer t = new Timer(x => GC.Collect(), null, 0, 2000);
                Console.ReadKey();
                t.Dispose();
            }
            ~MyFinalizeObject1()//将在Finalization Queue队列中的类首先放入 Freachable Queue中,然后调用终结器,然后再释放.
            {
                Console.WriteLine(this.GetType().ToString() + $" {this.Name} has been DC {CountFinalized++}");//类每次终结输出信息
                GC.ReRegisterForFinalize(this);//将类重新放入终结队列. 
            }
        }

        子模式:

  • 并发:占用更多资源,创建了额外的后台线程
  • 非并发----可以设定配置
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
          <probing privatePath="AuxFiles"/>
        </assemblyBinding>
        <gcServer enabled="true"/>//设定服务器模式
        <gcConcurrent enabled="false"/>//设定非并发模式
      </runtime>
        <startup>
            <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
        </startup>
    </configuration>

   自定义在低回收延迟模式进行绘画,动画等低延迟操作

private static void LowLatencyDemo()
        {
            GCLatencyMode OldMode = GCSettings.LatencyMode;
            System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                GCSettings.LatencyMode = GCLatencyMode.LowLatency;
                //自己的代码,低GC回收延迟
            }
            finally
            {
                GCSettings.LatencyMode = OldMode;
            }
        }

10-,强制垃圾回收GC.Collect(int Generation,GCCollectionMode mode,bool blocking)

11,监视应用程序内存:

     11.1 在内存中使用函数

~MyFinalizeObject1()//将在Finalization Queue队列中的类首先放入 Freachable Queue中,然后调用终结器,然后再释放.
        {
            Console.WriteLine(this.GetType().ToString() + $" {this.Name} has been DC {CountFinalized++}");//类每次终结输出信息
            Console.WriteLine($"CollectionCount:{GC.CollectionCount(0)}");
            Console.WriteLine($"GetTotalMemery:{GC.GetTotalMemory(false)}");
            GC.ReRegisterForFinalize(this);//将类重新放入终结队列. 
        }

      11.2打开性能监视器---PerfMon.exe

   然后查看+,添加.net clr memeroy,勾选显示描述.

image

12.使用需要特殊清理的类型.任何包含本机资源的类型都支持终结---Finalize

文件,网络连接,套接字,互诉体---都支持.


------------创建本机的托管资源类型时,强烈建议应该从System.Runtime.InteropServices.SafeHandle这个基类来进行派生.

[System.Security.SecurityCritical]
public abstract class SafeHandle : System.Runtime.ConstrainedExecution.CriticalFinalizerObject, IDisposable

该类的实现:

protected SafeHandle (IntPtr invalidHandleValue, bool ownsHandle);
参数
invalidHandleValue
IntPtr
无效句柄的值(通常为 0 或 -1)。 IsInvalid 的实现应对此值返回 true。
ownsHandle
Boolean
在终止阶段使 true 可靠地释放句柄,则为 SafeHandle;否则为 false(不建议使用)。
注解
如果 ownsHandle 参数 false,则永远不会调用 ReleaseHandle;因此,不建议使用此参数值,因为代码可能会泄漏资源。
//也就是说,invalidHandleValue,只是无效的Handle的值.此时,IsInvalid返回true;
另一个参数建议true,否则无法使用RelseaseHandle()函数,该函数用于释放资源.
 public abstract class SafeHandle : CriticalFinalizerObject, IDisposable
    {//只是本机资源句柄.
        protected IntPtr handle;
        //第二个参数指明,派生的对象被回收时,本机资源也被关闭.true---调用ReleaseHandle()函数.
        protected SafeHandle(IntPtr invalidHandlerValue, bool ownsHandle)
        {
            this.handle = invalidHandlerValue;
        }//第一个参数指明非实句柄的值0或-1,第二个指明,是否自定义释放句柄函数工作.
        protected void SetHandle(IntPtr handle)
        {
            this.handle = handle;
        }
        public void Dispose() { Dispose(true); }
        protected virtual void Dispose(Boolean disposing)
        {
            //默认实现忽略disposing参数
            //如果资源已经释放,那么返回.
            //如果ownsHandle为false,那么返回.
            //设置 标志位 来只是资源已经释放
            //调用虚方法 ReleaseHandle();
            //调用GC.SuppressFinalize(this)阻止调用Finalize方法
            //如果ReleaseHandle返回true,那么返回.
            //如果走到这一步,激活releaseHandleFaild托管调试助手(MDA)
        }
        ~SafeHandle() { Dispose(false); }
        protected abstract bool ReleaseHandle();//必须重写这个方法以实现释放资源代码

        public void SetHandleAsInvalid()
        {
            //调用GC.SuppressFinalize(this)方法来阻止调用Finalize方法.
        }
        public bool IsClosed
        {
            get { return true; } //返回指出资源是否释放的标志---Dispose(true)中设置.
        }
        public abstract bool IsInvalid//重写该属性,只是句柄值不代表资源,0或-1.
        {
            get;
        }
    }

safeHandle派生的类有:

  • SafeHandle 是操作系统句柄的抽象包装器类。 从此类派生比较困难。 但可以使用 Microsoft.Win32.SafeHandles 命名空间中可提供以下项的安全句柄的派生类。
  • 文件(SafeFileHandle 类)。
  • 内存映射文件(SafeMemoryMappedFileHandle 类)。
  • 管道(SafePipeHandle 类)。
  • 内存视图(SafeMemoryMappedViewHandle 类)。
  • 加密构造(SafeNCryptHandle、SafeNCryptKeyHandle、SafeNCryptProviderHandle和 SafeNCryptSecretHandle 类)。
  • 进程(SafeProcessHandle 类)。
  • 注册表项(SafeRegistryHandle 类)。
  • 等待句柄(SafeWaitHandle 类)。

以下是SafeHandleZeroOrMinusOneIsInvalid

//从 SafeHandle 派生的类---SafeHandleZeroOrMinusOneIsInvalid:SafeHandle

public abstract class SafeHandleZeroOrMinusOneIsInvalid : SafeHandle
    {
        protected SafeHandleZeroOrMinusOneIsInvalid(bool ownsHandle) : base(IntPtr.Zero, ownsHandle) { }//默认设定句柄为0.
        public override bool IsInvalid//只是句柄为0,或者-1时候,则为Invalid形式.
        {
             get
             {
                 if (base.handle == IntPtr.Zero) return true;
                 if (base.handle == (IntPtr)(-1)) return true;
                 return false;
             }
         }
     }

   以下是SafeFileHandle类

 public sealed class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        public SafeFileHandle(IntPtr preExistingHandle,bool ownsHandle) : base(ownsHandle) { base.SetHandle(preExistingHandle); }
        protected override bool ReleaseHandle()
        {
            return Win32Native.CloseHandle(base.handle);
        }
    }

13,使用包装了本机资源的类型

假定写代码创建一个临时文件,向其中写入一些字节,然后删除文件.会报出IOException错误,因为,在文件资源生存期无法删除此文件.

public class CallSafeHandle
    {
        public static void Demo1()
        {
            byte[] bytes = { 1, 2, 3, 4, 5 };
            FileStream fs = new FileStream("Temp.dat", FileMode.Create);
            fs.Write(bytes, 0, bytes.Length);
            File.Delete("Temp.dat");

        }
    }

将代码更改为如下----添加了,dispose();就可以正常运行了.

byte[] bytes = { 1, 2, 3, 4, 5 };
            FileStream fs = new FileStream("Temp.dat", FileMode.Create);
            fs.Write(bytes, 0, bytes.Length);
            fs.Dispose();
            File.Delete("Temp.dat");

注意:Dispose()函数只是释放资源,但是对象还是存在的.

public static void Demo1()
        {
            byte[] bytes = { 1, 2, 3, 4, 5 };
            FileStream fs = new FileStream("Temp.dat", FileMode.Create);
            try
            {
                fs.Write(bytes, 0, bytes.Length);
            }
            finally
            {
                if (fs != null) fs.Dispose();
            }

            File.Delete("Temp.dat");

        }

可以ji进一步更改为如下的形式  使用using.

public static void Demo1()
        {
            byte[] bytes = { 1, 2, 3, 4, 5 };



            using(FileStream fs = new FileStream("Temp.dat", FileMode.Create))//相当于 Try...Finally结构,必须实现IDispose接口
            {
                fs.Write(bytes, 0, bytes.Length);
            }

            File.Delete("Temp.dat");
        }

利用 StreamWriter 进行文件读写操作.

public static void Demo2()
        {
            using(StreamWriter sw=new StreamWriter(new FileStream("DataFile.txt", FileMode.OpenOrCreate)))
            {
                sw.Write("Hi there");
            }

        }
    }

注意:StreamWrite只会把数据写入到缓冲区里面,所以需要Dispose()函数才能真正写入进去.或者调用Flush().


14,GC的其他功能----使用这个来防止以下的情况:

1,托管类很小,资源类很大,比如位图,这样会造成内存用量很大.使用这个,可以提示GC提前,并且更频繁的进行垃圾回收.

  • GC.AddMemoryPressure(int64 byteallocated)
  • GC.RemoveMemoryPressure(Int64 byteallocated)
    private sealed class BigNativeResource
            {
                private int m_size;
                public BigNativeResource(int size)
                {
                    if (m_size > 0) GC.AddMemoryPressure(m_size);//每个类提示实际消耗内存.
                    Console.WriteLine("BigNativeResource create.");
                }
                ~BigNativeResource()
                {
                    if (m_size > 0) GC.RemoveMemoryPressure(m_size);//每个类提示实际取消内存.
                    Console.WriteLine("BigNativeResource destroy.");
                }
            }

2,设定有限制的资源----使用HandleCollector这个类的问题.

private sealed class LimitedResource
        {
            private static readonly HandleCollector s_hc = new HandleCollector("LimitedResource", 2);
            public LimitedResource()
            {
                s_hc.Add();
                Console.WriteLine($"LimitedResource create {s_hc.Count}");
            }
            ~LimitedResource()
            {
                s_hc.Remove();
                Console.WriteLine("LimitedResource destroy. Count={0}", s_hc.Count);
            }
        }
控制一个Handle Collector,并且初始化为2,也就是说只要该对象增加值达到2个,就开始垃圾回收.
HandleCollectorDemo
LimitedResource create 1
LimitedResource create 2
LimitedResource create 3
LimitedResource create 4
LimitedResource destroy. Count=4
LimitedResource destroy. Count=3
LimitedResource destroy. Count=2
LimitedResource destroy. Count=1
LimitedResource create 1
LimitedResource create 2
LimitedResource create 3
LimitedResource create 4
LimitedResource destroy. Count=4
LimitedResource destroy. Count=3
LimitedResource destroy. Count=2
LimitedResource create 3
LimitedResource create 3
LimitedResource destroy. Count=2
LimitedResource destroy. Count=1
LimitedResource destroy. Count=0

程序中改成了4个对象.也就是该类,通过控制数量来触发垃圾回收.


15,终结的工作原理:

有终结器的对象在垃圾回收的时候,从 Finalization Queue移动到Freachable Queue,然后执行Finalize方法,然后,在清空Freachale队列.所以,对于某个有终结器的对象,其有可能要求不止进行2次垃圾回收.


16,手动监视和控制对象的生存期----CLR为每个AppDomain都提供了一个GC句柄表(GC Handle table)---该表使用

GCHandle类在表中添加删除记录项.

GCHandle 结构与 GCHandleType 枚举一起使用,以创建对应于任何托管对象的句柄。 此句柄可以是以下四种类型之一: WeakWeakTrackResurrectionNormalPinned。 如果分配了句柄,则在非托管客户端持有唯一引用时,可以使用它来防止垃圾回收器收集托管的对象。 如果没有这样的句柄,则在代表非托管客户端完成其工作之前,垃圾回收器可以收集该对象。

你还可以使用 GCHandle 来创建一个固定的对象,该对象返回内存地址以防止垃圾回收器将对象移动到内存中。

public struct GCHandle
    {
        public static GCHandle Alloc(object value);
        public static GCHandle Alloc(object value, GCHandleType type);
        //静态方法,用于将一个GCHandle-->IntPtr
        public static explicit operator IntPtr(GCHandle value);
        public static IntPtr ToIntPtr(GCHandle value);
        //静态方法,用于比较两个GCHandle
        public static bool operator ==(GCHandle a, GCHandle b);
        public static bool operator !=(GCHandle a, GCHandle b);
        //实列方法,用于释放表中的记录项,索引设为0---也就是将IntPtr设为0
        public void Free();
        //实列属性,用于获得记录项对象
        public object Target { get; set; }
        //判断索引是否被分配.索引不为0则返回true
        public bool IsAllocated { get; }//调用Alloc 为true,调用Free,为False
        //对于已固定记录项,这个方法返回对象地址
        public IntPtr AddOfPinnedObject();

    }

GCHandleType 有以下四个类型:

  • Weak:允许监视生存期,Finalize对象可能执行,可能没执行.
  • WeakTrackResurrection:允许监视生存期,Finalize对象已执行.对象内存已回收
  • Normal:允许控制对象生存期(默认).该对象必须在内存里,垃圾回收时,可以移动.
  • Pinned:允许控制对象生存期(默认).该对象必须在内存里,垃圾回收时,不可移动,对象在内存中地址固定.

垃圾


回收流程:

1 GC标记所有可达对象,然后扫描GC句柄表,然后所有Normal和Pinned的记录项被看成根,所有的相关对象变为可达.不进行回收.
2

GC扫描句柄表中Weak记录项,如果其对象不可达,则将该记录项的引用值改为null.

在终结器运行之前,Weak 引用归零,因此即使终结器使该对象复活,Weak 引用仍然是归零的。

3 GC扫描终结列表,将终结列表对象(带析构函数的类的对象)移动到freachable队列中,并且这些对象被标记,变成可达对象.
4

GC扫描GC句柄表WeakTrackResurrection记录项.如果其对象不可达,则记录项引用值更改为NULL

该句柄类型类似于 Weak,但如果对象在终结过程中复活,此句柄不归零。

5

GC进行内存压缩,Pinned对象不会被移动




GCHandle 使用情况-----Normal和Pinned入手

使用 GCHandle.Alloc 方法注册对象,然后将返回的GcHandle实列转换为IntPtr...再将这个指针传递给本机代码.

本机代码回调托管代码时,托管代码将传递的IntPtr转型为GCHandle,然后,查询其Target属性来获得托管对象的引用.

本机代码不需要时,可以使用GCHandle.Free使其注销.

使用Fixed…使垃圾回收的时候

unsafe public static void Demo1()
        {
            for (int x = 0; x < 10000; x++) new object();
            IntPtr orgptr;
            byte[] bytes = new byte[1000];
            fixed(Byte* p = bytes)//告诉GC不要移动该对象.
            {
                orgptr = (IntPtr)p;
            }
            GC.Collect();
            fixed(byte* p = bytes)//告诉GC不要移动该对象.
            {
                Console.WriteLine(orgptr == (IntPtr)p ? "Yes" : "NO");
            }
        }
    }

使用该关键字使得指向的对象地址固定,不被GC移动.(指针指向的对象地址必须固定)


Weak Reference<T>---

方法

Equals(Object)

确定指定对象是否等于当前对象。

(继承自 Object)
Finalize()

丢弃对当前 WeakReference<T> 对象表示的目标的引用。

GetHashCode()

用作默认哈希函数。

(继承自 Object)
GetObjectData(SerializationInfo, StreamingContext)

用序列化当前 SerializationInfo 对象所需的所有数据填充 WeakReference<T> 对象。

GetType()

获取当前实例的 Type

(继承自 Object)
MemberwiseClone()

创建当前 Object 的浅表副本。

(继承自 Object)
SetTarget(T)

设置 WeakReference<T> 对象引用的目标对象。

ToString()

返回表示当前对象的字符串。

(继承自 Object)
TryGetTarget(T)

尝试检索当前 WeakReference<T> 对象引用的目标对象。

本质是一个GCHandle包装器.其构造器调用Alloc方法.SetTarget设定Target对象.TryGetTarget用于获取Target对象.

终结器调用Free方法.

WeakReference<T>含义是,对于对象的引用时弱引用.也就是,其并不会阻止GC回收该对象.


public static void Go()
        {
            ObjectA objA = new ObjectA("objA");

            WeakReference<ObjectA> weakReference = new WeakReference<ObjectA>(objA);
            ShowObject(weakReference);
           // weakReference.TryGetTarget(out ObjectA objectA1);
            objA = null;
            //objectA1 = null; 注释的时候,显示对象没有被回收,因为有引用,是能这句的时候,对象没有引用,所以下面的没有了.
           // GC.Collect();

            ShowObject(weakReference);


        }

没有进行垃圾回收,显示

obj1 is Exsit!
obj1 is Exsit!

有进行垃圾回收显示

obj1 is Exsit!
obj1 is not exsist!(垃圾回收后,该对象不存在了).


17.使用ConditionalWeakTable

[System.Runtime.InteropServices.ComVisible(false)]
public sealed class ConditionalWeakTable<TKey,TValue> where TKey : class where TValue : class

public static void GO()
        {
            var mc1 = new ManagedClass();
            var mc2 = new ManagedClass();
            var mc3 = new ManagedClass();

            var cwt = new ConditionalWeakTable<object, ClassData>();
            cwt.Add(mc1, new ClassData());
            cwt.Add(mc2, new ClassData());
            cwt.Add(mc3, new ClassData());

            var wr2 = new WeakReference(mc2);
            mc2 = null;

            GC.Collect();
            Print(wr2.Target, cwt);
           var b1= wr2.Target != null;
            //if (b1) Console.WriteLine("aaa");//注意,在if 语句里面,似乎这个弱引用被使用了???
        }
        class ManagedClass
        {
        }

        class ClassData
        {
            public DateTime CreationTime;
            public object Data;

            public ClassData()
            {
                CreationTime = DateTime.Now;
                this.Data = new object();
            }
        }
        private static void Print(object Target, ConditionalWeakTable<object, ClassData> cwt)
        {
            ClassData data = null;

            if (Target == null)
                Console.WriteLine("No strong reference to mc2 exists.");
            else if (cwt.TryGetValue(Target, out data))
                Console.WriteLine("Data created at {0}", data.CreationTime);
            else
                Console.WriteLine("mc2 not found in the table.");
        }
    }

-------这里有个关键问题.似乎在if中使用wr2,则这个对象不会被收集,所以,需要尽量在函数中使用wr2,否则其可能还存在.

No strong reference to mc2 exists.
答案2:如果If语句没有被注释.
Data created at 2020/2/12 19:59:41
aaa



猜你喜欢

转载自www.cnblogs.com/frogkiller/p/12300586.html