重庆熊猫 Loading

.NET教程 - 垃圾回收(Garbage Collection)

更新记录
转载请注明出处:
2022年10月7日 发布。
2022年10月7日 从笔记迁移到博客。

new运算符在CIL中的操作过程

指令为:

new obj

作用:分配托管堆的内存,并将指针下移,如图:如果托管堆的内存不足,就进行垃圾回收,所以没有足够的空间就进行垃圾回收

image

显式让GC回收内存

GC.Collect()

将实例对象赋值为null在CIL操作过程

只是切断对象的引用,并不会直接进行垃圾回收

应用程序根(Roots)

Application Root类似于一个指针,指向托管对堆中的各种资源

根(Root)是使对象保持活动状态的东西

如果对象不是由根直接或间接引用的,则可以进行垃圾回收

A root is one of the following:

A local variable or parameter in an executing method (or in any method in its call stack)

A static variable

An object on the queue that stores objects ready for finalization (see the next section)

根示意图:

image

作用:

在进行垃圾回收时,检查资源的根,没有根的资源,标记为垃圾

其余有跟的资源将进行从新的排列,类似于链表,如图

排序前:

image

排序后:

image

对象的代

对象的代是垃圾回收中的一个分层思维,每个对象都有一个代

一个对象有三种代:

第0代:标记为不回收的对象

第1代:标记为回收的对象,但是上一次有足够的内存,未进行回收的对象

第2代:上一次垃圾回收的上一次的垃圾回收仍没有被回收的对象

0代回收最快,未被回收的数据代+1,最高为2

第0代和第1代统称为暂时代

作用:

垃圾回收中,回收先检查第0代,检查应用程序根后区分垃圾和正常值

回收垃圾后,未回收的标记为第1代

查看是否具有足够的空间,不足的继续进行第1代垃圾回收,直到第2代

如图所示

image

处理disposable释放的最佳实践

当对象被disposed后,不可以再次使用对象

多次调用Dispose方法不会抛出异常

如果A对象包含B对象,A对象调用Dispose方法后会调用B的Dispose方法

以上规则不是强制性的,但便于日后维护程序

Close方法 & Stop方法

Some types define a method called Close in addition to Dispose

在数据库的IDbConnection中

Closed connection can be re-Opened; a Disposed connection cannot

在Windows Form的Dialog中

Close Dialog hides it; Dispose Dialog releases its resources

在Windows Runtime (WinRT) libraries中

Close is considered identical to Dispose—in fact

the runtime projects methods called Close into methods called Dispose

Close方法 & Dispose方法

Some classes define a Stop method

A Stop method may release unmanaged resources, like Dispose

but unlike Dispose, it allows for re-Starting

Garbage Collection & WinRT

WinRT依靠引用计数机制(reference-counting mechanism)来释放内存,而不是依赖于自动GC

Automatic Garbage Collection

GC不会立即对未被使用的对象进行垃圾回收

GC不会每次收集都收集所有垃圾

GC尝试在进行垃圾收集的时间与应用程序的内存消耗(工作集)之间取得平衡

GC如何工作(How the GC Works)

第一步

在分配了一定的内存阈值之后或在其他时候

GC会在执行内存分配(通过new关键字)时启动垃圾回收或在其他时间减少应用程序的内存占用

也可以通过调用System.GC.Collect手动启动此过程

在垃圾回收期间,所有线程都可以被冻结

第二步

GC从其根对象引用开始,然后遍历对象图,将其触摸的所有对象标记为可访问

完成此过程后,所有未标记的对象都被视为未使用,并受到垃圾回收

第三步

没有析构器的未使用对象将立即被丢弃

GC完成后,将带有析构器的未使用对象排队入析构器线程中进行处理

然后,这些对象将有资格在下一代对象的GC中进行收集(除非已复活)

第四步

然后将剩余的还存在的对象移到堆的开头(压缩),从而为更多对象释放空间

这种压缩有两个目的:

避免内存碎片,并允许GC在分配新对象时采用非常简单的策略,即始终在堆末尾分配内存

这避免了维护空闲内存段列表的潜在耗时任务

第五步

如果在垃圾回收后没有足够的空间为新对象分配内存

并且操作系统无法授予更多内存,则会引发OutOfMemoryException

GC优化技术(Optimization Techniques)

说明

GC结合了各种优化技术,以减少垃圾收集时间

代收集(GENERATIONAL COLLECTION)

Basically, the GC divides the managed heap into three generations

Objects that have just been allocated are in Gen0

objects that have survived one collection cycle are in Gen1

all other objects are in Gen2

注意:

Gen0 and Gen1 are known as ephemeral (short-lived) generations

回收内存的示意图

image

大对象堆(THE LARGE OBJECT HEAP)

GC对大于特定阈值的对象使用称为大对象堆(LOH,Large Object Heap)的单独堆

如果没有LOH,分配一系列16MB对象可能会在每次分配后触发Gen0集合

The LOH is nongenerational,all objects are treated as Gen2

工作站和服务器集合(WORKSTATION VERSUS SERVER COLLECTION)

.NET Core 提供了2中GC模式:workstation、server

workstation模式:

​ 应用程序中默认的GC模式

​ 只有一个堆和GC

Server模式:

​ CLR为每个内核(Core)分配一个单独的堆(Heap)和GC

​ 这会加速垃圾回收,但会消耗更多的CPU和内存资源

​ 因为每个核心都是一个单独的线程

​ 注意:仅在多核心系统上可用,单核心系统上会被忽略

可以切换到server模式通过在.csproj配置文件中加入配置项

<PropertyGroup>
  <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

通过编译程序后会生成.runtimeconfig.json文件,并会自动写入

"runtimeOptions": {
"configProperties": {
    "System.GC.Server": true

BACKGROUND COLLECTION(后台收集)

CLR enables background collection by default

可以在.csproj配置文件中禁用后台收集

注意:禁用后台收集后,收集垃圾会导致程序阻塞

<PropertyGroup>
  <ConcurrentGarbageCollection>false</ConcurrentGarbageCollection>
</PropertyGroup>

通过应用程序编译后,会在.runtimeconfig.json文件中写入:

"runtimeOptions": {
"configProperties": {
    "System.GC.Concurrent": false,

GC NOTIFICATIONS(GC 通知)

如果禁用了后台收集,则可以要求GC在即将进行完整(阻塞)收集之前进行通知

在运行时优化垃圾回收(Tuning Garbage Collection at Runtime)

内存压力(Memory Pressure)

运行时会根据多种因素(包括计算机上的总内存负载)来决定何时启动垃圾回收

数组池(Array Pooling)

如果应用程序频繁实例化数组,则可以通过数组池避免大部分垃圾回收开销

Array pooling is new to .NET Core 3

通过租用(renting)一个数组池中的数组来工作,然后将其返回到数组池中以供重用

可以自定义数组池也可以使用全局共享数组池

所在命名空间

using System.Buffers;

实例:自定义数组池

//创建数组池并分配数组
ArrayPool<int> arrayPool1 = ArrayPool<int>.Create();
int[] someArray1 = arrayPool1.Rent(100); // 100 bytes
//用完后归还数组
arrayPool1.Return(someArray1);

实例:使用全局共享的数组池

//直接创建数组池并分配数组
int[] someArray2 = ArrayPool<int>.Shared.Rent(100);  // 100 bytes
//用完后归还数组
ArrayPool<int>.Shared.Return(someArray2);

托管内存泄漏(Managed Memory Leaks)

说嘛

在非托管语言(例如C ++)中

必须记住当不再需要对象时需要手动释放内存

否则会导致内存泄漏

应用程序在其生命周期中消耗越来越多的内存,直到最终必须重新启动它为止

托管内存泄漏是由于未使用的对象由于未使用或遗忘的引用而保持活动状态而导致的

处理办法

在使用需要手动释放内存的地方记得调用Dispose()方法

以Timers为例,忘记计时器(Timers)释放会导致内存泄漏

在使用Timers等需要手动释放内存的地方记得调用Dispose()方法

实例:在自定义类型中调用Dispose()

class Foo : IDisposable
{
  // ...
  public void Dispose() { _timer.Dispose(); }
}

最佳实践

A good guideline is to implement IDisposable yourself

if any field in your class is assigned an object that implements IDisposable

诊断内存泄漏(Diagnosing Memory Leaks)

获得代码使用总内存量

long memoryUsed = GC.GetTotalMemory (true);

常用调试工具

Microsoft’s CLR Profiler

windbg.exe

SciTech’s Memory Profiler

Red Gate’s ANTS Memory Profiler

弱引用(Weak References)

实例:

var sb = new StringBuilder ("this is a test");
var weak = new WeakReference (sb);
Console.WriteLine (weak.Target);     // This is a test

实例:弱引用被回收后不可以再使用

var weak = new WeakReference (new StringBuilder ("weak"));
Console.WriteLine (weak.Target);   // weak
GC.Collect();
Console.WriteLine (weak.Target);   // (nothing)

Forcing Garbage Collection(强制垃圾回收)

使用GC.Collect()方法就可以了

GC.Collect()    //强制回收所有代
GC.Collect(0)   //强制回收第0代
GC.Collect(1)   //强制回收第1代
GC.Collect(2)   //强制回收第2代

让被析构后的对象也被收集

//让被析构后的对象也被收集
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

.NET 1.0 和.NET 3.5的CLR垃圾回收

使用:并发垃圾回收

工作过程:垃圾回收器挂起当前所有线程,以防资源冲突。执行垃圾回收线程,进行托管堆的垃圾回收

.NET 4.0 的垃圾回收

使用:后台垃圾回收

工作过程:非暂时态(2)对象使用后台垃圾回收。暂时态(0、1)对象使用专用垃圾回收线程

推迟加载

System.Lazy<Type>

调用时才加载

对象中包含一些特殊的成员,这些成员在使用的时候才进行实例化

作用:延迟加载对象成员、减少运行时的内存消耗、时间延迟

System.GC类

GC.GetTotalMemory(Boolean) 获得目前托管堆大小,Boolean决定等待垃圾回收

GC.MaxGeneration 获得支持最大的代,从0开始

GC.GetGeneration(object) 获得对象的代

GC.Collet() 执行强制垃圾回收

GC.Collet(int) 执行强制垃圾回收,指定代

GC.Collet(int,GCCollectionMode) 执行强制垃圾回收,指定代和回收模式

GC.WaitForPendingFinalizers() 在执行垃圾回收时,挂起当前线程

GC.CollectionCount() 获得代被回收的次数

GC.SuppressFinalize(object) 垃圾回收时不执行object的析构函数

释放资源的几种方法

1、重写Finalize()

2、使用析构函数

3、使用IDisponsable接口

4、使用using(){ //code}释放

析构函数性能问题

使用析构函数仍然不能有效控制非托管资源,只能在垃圾回收时自动释放资源

重写了析构函数的对象分配内存时将加入终结队列,则释放时至少进行2次垃圾回收(垃圾回收/终结队列),会导致性能损失

IDisposable接口

使用该接口可以手动调用Dispose方法释放资源

.NET内置类型很多都支持Dispose方法

占用非常多的资源,优先调用该方法,提高运行效率,而不是等待垃圾回收

使用Dispose方法时,应该在内部调用GC.SuppressFinalize(this),使垃圾回收时不调用析构函数

posted @ 2022-10-07 09:49  重庆熊猫  阅读(681)  评论(0编辑  收藏  举报