1,托管堆基础

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

2,从托管堆创造对象

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

3,垃圾回收算法

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

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

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

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

cir from c# 托管堆和垃圾回收的更多相关文章

  1. 【C#进阶系列】21 托管堆和垃圾回收

    托管堆基础 一般创建一个对象就是通过调用IL指令newobj分配内存,然后初始化内存,也就是实例构造器时做这个事. 然后在使用完对象后,摧毁资源的状态以进行清理,然后由垃圾回收器来释放内存. 托管堆除 ...

  2. .NET 托管堆和垃圾回收

       托管堆基础 简述:每个程序都要使用这样或那样的资源,包括文件.内存缓冲区.屏幕空间.网络连接.....事实上,在面向对象的环境中,每个类型都代表可供程序使用的一种资源.要使用这些资源,必须为代表 ...

  3. 【CLR】解析CLR的托管堆和垃圾回收

    目录结构: contents structure [+] 为什么使用托管堆 从托管堆中分配资源 托管堆中的垃圾回收 垃圾回收算法 代 垃圾回收模式 垃圾回收触发条件 强制垃圾回收 监视内存 对包装了本 ...

  4. 重温CLR(十五) 托管堆和垃圾回收

    本章要讨论托管应用程序如何构造新对象,托管堆如何控制这些对象的生存期,以及如何回收这些对象的内存.简单地说,本章要解释clr中的垃圾回收期是如何工作的,还要解释相关的性能问题.另外,本章讨论了如何设计 ...

  5. CLR via C# 读书笔记-21.托管堆和垃圾回收

    前言 近段时间工作需要用到了这块知识,遂加急补了一下基础,CLR中这一章节反复看了好多遍,得知一二,便记录下来,给自己做一个学习记录,也希望不对地方能够得到补充指点. 1,.托管代码和非托管代码的区别 ...

  6. 托管堆和垃圾回收(GC)

    一.基础 首先,为了深入了解垃圾回收(GC),我们要了解一些基础知识: CLR:Common Language Runtime,即公共语言运行时,是一个可由多种面向CLR的编程语言使用的"运 ...

  7. C#托管堆和垃圾回收

    垃圾回收 值类型 每次使用都有对应新的线程栈 用完自动释放 引用类型 全局公用一个堆 因此需要垃圾回收 操作系统 内存是链式分配 CLR 内存连续分配(数组) 要求所有对象从 托管堆分配 GC 触发条 ...

  8. 如何管好.net的内存(托管堆和垃圾回收)

    一:C#标准Dispose模式的实现 需要明确一下C#程序(或者说.NET)中的资源.简单的说来,C#中的每一个类型都代表一种资源,而资源又分为两类: 托管资源:由CLR管理分配和释放的资源,即由CL ...

  9. NET的堆和栈04,对托管和非托管资源的垃圾回收以及内存分配

    在" .NET的堆和栈01,基本概念.值类型内存分配"中,了解了"堆"和"栈"的基本概念,以及值类型的内存分配.我们知道:当执行一个方法的时 ...

随机推荐

  1. maven常用的远程仓库地址

    <mirror> <id>nexus-aliyun</id> <name>Nexus aliyun</name> <url>ht ...

  2. 动画 | 什么是平衡二分搜索树(AVL)?

    二分搜索树又名有序二叉查找树,它有一个特点是左子树的节点值要小于父节点值,右子树的节点值要大于父节点值.基于这样的特点,我们在查找某个节点的时候,可以采取二分查找的思想快速找到这个节点,时间复杂度期望 ...

  3. Excel Application操作指南

    概述 Application对象是Microsoft Office Excel 2007对象模型中最高级别的对象,表示Excel程序自身.Application对象提供正在运行的程序的信息.应用于程序 ...

  4. linux 为动态分配的Virtualbox虚拟硬盘扩容

    如何为动态分配的Virtualbox虚拟硬盘扩容 查看虚拟硬盘是否是动态分配大小 打开虚拟机的设置界面,在左侧栏点击存储.在存储树下面选择你的虚拟硬盘.在右边可以看见虚拟硬盘的信息.在下面可以看见,我 ...

  5. linux DNS 服务器 配置

    1:named.conf 2:正向区域配置文件 3:反向域名解析文件

  6. java web 各个文件夹命名原因

    今天突然被同学问然后就发现,自己有很多的疑问: (1) 为什么servlet的配置文件,命名为 web.xml , 内部是如何读取的,原因就是他内度的工作原理 (2) webINF Src 文件为什么 ...

  7. SpringBoot系列专栏

    学会使用SpringBoot能够极大地提升Spring应用的开发效率,可以说是目前开发应用Java必需掌握的工具之一,而且SpringBoot也是微服务应用的基础,只有学会了SpringBoot,你才 ...

  8. mysql 1071错误,原因是Mysql的字段设置的太长了

    mysql 1071错误,原因是Mysql的字段设置的太长了 mysql 1071错误经过查询才知道,是Mysql的字段设置的太长了,于是我把这两个字段的长度改了一下就好了. 建立索引时,数据库计算k ...

  9. 获取PHP类的所有属性和所有方法,可通过反射机制

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php   class Class1{     public $var1 = 'v ...

  10. react中 如何使用图片

    //render 第一种方法:先const一个对象,把需要应用图片的dom上的style写入对象中, 然后在return()中使用style关键字赋值为预先定义的那个style对象 const bgG ...