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. Linux下利用Ant调用Jmeter脚本生成HTML测试报告

    今天我们学习如何利用Ant调用Jmeter脚本,并将生成的 jtl 文件转换为 HTML 格式的测试报告. 准备工作 需要在Linux上提前安装好 JDK. Jmeter 和 Ant. 1,JDK(可 ...

  2. Ubuntu固定多个静态ip

    步骤: 1.sudo vim /etc/network/interfaces 加入下列内容 auto eth0#此处查看自己的ip信息是eth0还是eth1等等 iface eth0 inet sta ...

  3. java web 项目中基础技术

    1. 选择版本控制器(git, svn) 2. 用户登录的时候, 你需要进行认证, 权限受理 可以使用 spring shiro 框架,进行上面的工作 3. 过滤器(filter),监听器(liste ...

  4. 图像GIST特征和LMGIST包的python实现(有github)

    1什么是Gist特征        (1) 一种宏观意义的场景特征描述        (2) 只识别"大街上有一些行人"这个场景,无需知道图像中在那些位置有多少人,或者有其他什么对 ...

  5. tomcat 持久区溢出

    知识点:堆内存设置(JVM堆内存)java的堆内存分为两块:permantspace(持久带) 和 heap spaceOOM1.年老代溢出,表现为:java.lang.OutOfMemoryErro ...

  6. Vmware初次安装虚拟机需要做的一些网络配置——nat模式与桥接模式

    一.本机设置: 1.首先点击图中红线区域: 2.点击网络适配器  3.会出现如下区域: 4.网卡开启后设置ip地址,此处设置的ip和本机的ip没有关系,设置成你虚拟机里面运行的计算机需要的ip地址网段 ...

  7. 林大妈的JavaScript进阶知识(一):对象与内存

    JavaScript中的基本数据类型 在JS中,有6种基本数据类型: string number boolean null undefined Symbol(ES6) 除去这六种基本数据类型以外,其他 ...

  8. pandas行列显示不全的问题

    https://blog.csdn.net/rookie_is_me/article/details/83991490

  9. Jumpserver:跳板机

    简介 jumpserver是github上的一个开源项目,其能有效的对服务器.用户进行分组,实现用户-系统用户-服务器的对应权限控制,并结合审计.日志等功能,据说是 4A 级的专业运维审计系统,系统提 ...

  10. SpringBoot2 整合Kafka组件,应用案例和流程详解

    本文源码:GitHub·点这里 || GitEE·点这里 一.搭建Kafka环境 1.下载解压 -- 下载 wget http://mirror.bit.edu.cn/apache/kafka/2.2 ...