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

时间:2023-03-08 18:50:07
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. cir from c# 托管堆和垃圾回收

3,垃圾回收算法

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

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

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

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,勾选显示描述.

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

        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