GC就是Garbage Collector 垃圾回收或者收集,把没用的或者用完的给干掉。
Garbage Collector(垃圾收集器,在不至于混淆的情况下也成为GC)以应用程序的root为基础,遍历应用程序在Heap(堆)上动态分配的所有对象,通过识别它们是否被引用来确定哪些对象是已经死亡的、哪些仍需要被使用。已经不再被应用程序的root或者别的对象所引用的对象就是已经死亡的对象,即所谓的垃圾,需要被回收。
Heap分为3个代龄区域,相应的GC有3种方式: # Gen 0 collections, # Gen 1 collections, #Gen 2 collections。如果Gen 0 heap内存达到阀值,则触发0代GC,0代GC后Gen 0中幸存的对象进入Gen1。如果Gen 1的内存达到阀值,则进行1代GC,1代GC将Gen 0 heap和Gen 1 heap一起进行回收,幸存的对象进入Gen2。
一次垃圾收集简单的说,是按照下面的流程来走的:
1) 挂起那些正在执行.net 调用的线程(比如分配一个对象或修改堆上的对象),执行原生调用的线程在他们返回到托管代码的时候挂起。
2) 决定哪些对象在当前代中是可以被垃圾收集的。通过询问JIT、EE stack walker、handle table和finalize queue来完成哪些对象还在使用中。
3) 删除所有标记删除的对象或在堆还没有合并的时候,添加一个空白到自由列表中(free list)
4) 合并移动残留下来的对象到堆的后端(代价最为昂贵)
5) 重新启动所有线程
2代GC将Gen 0 heap、Gen 1 heap和Gen 2 heap一起回收,Gen 0和Gen 1比较小,这两个代龄加起来总是保持在16M左右;Gen2的大小由应用程序确定,可能达到几G,因此0代和1代GC的成本非常低,2代GC称为full GC,通常成本很高。粗略的计算0代和1代GC应当能在几毫秒到几十毫秒之间完成,Gen 2 heap比较大时,full GC可能需要花费几秒时间。大致上来讲.NET应用运行期间,2代、1代和0代GC的频率应当大致为1:10:100。
1).托管代码:无需也无法人为干预内存回收工作的代码。会自动调用GC进行垃圾回收,我们日常所写的研发程序代码大多数都是分托管代码,没有终结器(Finalize)。
像简单的int,string,float,DateTime等等,.net中超过80%的资源都是托管资源。
说到托管代码的自动垃圾回收机制,有以下几个知识点需要知晓。
首先,什么时候发生垃圾回收?
①.发生垃圾回收的时间有以下情况:
(1)第0代满。
(2)代码显式调用GC.Collect方法 ——System.GC是一个表示垃圾收集器的.NET基类, Collect()方法则调用垃圾收集器。但是,这种方式适用的场合很少,(难道销毁一个对象就让垃圾回收检查一便内存?)
(3)Windows报告内存不足 —— CLR注册了 Win32 CreateMemoryResourceNotification和QueryMemoryResourceNotification监视系统总体内存使用情况,如果收到Window报告内存不足的通知,强行执行GC。
(4)CLR卸载AppDomain
(5)CLR关闭,程序关闭
②关于堆和栈
.NET中的所有类型都是(直接或间接)从System.Object类型派生的。
CTS中的类型被分成两大类——引用类型(reference type,又叫托管类型[managed type]),分配在内存堆上;值类型(value type),分配在堆栈上。
值类型在栈里,先进后出,值类型变量的生命有先后顺序,这个确保了值类型变量在退出作用域以前会释放资源。比引用类型更简单和高效。堆栈是从高地址往低地址分配内存。
引用类型分配在托管堆(Managed Heap)上,声明一个变量在栈上保存,当使用new创建对象时,会把对象的地址存储在这个变量里。托管堆相反,从低地址往高地址分配内存。
2).非托管资源:与托管代码形成对立面,这部分资源由垃圾回收器可以跟踪封装非托管资源的对象的生存期,可以人为的干涉内存回收工作,但是不会自动调用GC自行进行垃圾回收,不过还好.net Framework提供了Finalize()方法(终结器),默认情况下,Finalize 方法不执行任何操作。如果您要让垃圾回收器在回收对象的内存之前对对象执行清理操作,您必须在类中重写 Finalize 方法,它允许在垃圾回收器回收该类资源时,适当的清理非托管资源。
在编程中,并不建议进行override方法Finalize(),因为,实现 Finalize 方法或析构函数对性能可能会有负面影响。理由如下:用 Finalize 方法回收对象使用的内存需要至少两次垃圾回收,当垃圾回收器回收时,它只回收没有终结器(Finalize方法)的不可访问的内存,这时他不能回收具有终结器(Finalize方法)的不可以访问的内存。它改将这些对象的项从终止队列中移除并将他们放置在标记为“准备终止”的对象列表中(即可以理解为去掉了终结器),该列表中的项指向托管堆中准备被调用其终止代码的对象,等到下次垃圾回收器进行回收操作时会将其回收。
与非托管资源相关的几个知识点如下:
①. 以下是几种常见的非托管资源: ApplicationContext,Context,Cursor,FileStream,Font,Icon,Image,Matrix,Object,OdbcDataReader,
OleDBDataReader,Pen,Regex,Socket,StreamWriter,Timer,Tooltip 等等,这些东西相比大家都并不陌生,只是可能在用的时候并未注意过而已。
②.NET Framework / netcore 提供 Object.Finalize 方法,它允许对象在垃圾回收器回收该对象使用的内存时适当清理其非托管资源。默认情况下,Finalize 方法不执行任何操作。如果您要让垃圾回收器在回收对象的内存之前对对象执行清理操作,您必须在类中重写 Finalize 方法。
大家由此可以发现在实际的编程中根本无法override方法Finalize(),在C#中,可以通过析构函数自动生成 Finalize 方法和对基类的 Finalize 方法的调用。
例如:
//析构函数~MyClass()
{
// Perform some cleanup operations here.
}
//该代码隐式翻译为下面的代码。
protected override void Finalize()
{
try
{
// Perform some cleanup operations here.
}
finally
{
base.Finalize();
}
}
③.析构函数
前面介绍了构造函数可以指定必须在创建类的实例时进行的某些操作,在垃圾收集器删除对象时,也可以调用析构函数。由于执行这个操作,所以析构函数初看起来似乎是放置
释放未托管资源、执行一般清理操作的代码的最佳地方。但是,事情并不是如此简单。由于垃圾回首器的运行规则决定了,不能在析构函数中放置需要在某一时刻运行的代码,如果对象占用了宝贵而重要的资源,应尽可能快地释放这些资源,此时就不能等待垃圾收集器来释放了.
对象的Finalizer被执行的时间是在对象不再被引用后的某个不确定的时间。
Finalizer的使用有性能上的代价。需要Finalization的对象不会立即被清除,而需要先执行Finalizer.Finalizer,不是在GC执行的线程被调用。GC把每一个需要执行Finalizer的对象放到一个队列中去,然后启动另一个线程来执行所有这些Finalizer,而GC线程继续去删除其他待回收的对象。在下一个GC周期,这些执行完Finalizer的对象的内存才会被回收。(前文已有提及)
由于GC并不是实时性的,这会造成系统性能上的瓶颈和不确定性。所以有了IDisposable接口,IDisposable接口定义了Dispose方法,这个方法用来供程序员显式调用以释放非托 管资源。使用using语句可以简化资源管理。
Dispose()的执行代码显式释放由对象直接使用的所有未托管资源,并在所有实现IDisposable接口的封装对象上调用Dispose()。这样,Dispose()方法在释放未托管资源时提供了精确的控制。
假定有一个类ResourceGobbler,它使用某些外部资源,且执行IDisposable接口。如果要实例化这个类的实例,使用它,然后释放它,就可以使用下面的代码:
ResourceGobbler theInstance = new ResourceGobbler();
// 这里是theInstance 对象的使用过程
theInstance.Dispose();
如果在处理过程中出现异常,这段代码就没有释放theInstance使用的资源,所以应使用try块,编写下面的代码:
ResourceGobbler theInstance = null;
try
{
theInstance = new ResourceGobbler();
// 这里是theInstance 对象的使用过程
}
finally
{
if (theInstance != null) theInstance.Dispose();
}
如果是完美的解决办法就是用using
四:GC模式与适合的应用程序
一共有三种模式的GC,分别对应优化不同类型的应用程序。
①Server GC
这种类型的GC是针对服务器端高吞吐量和高扩展性进行优化的,那情况是一种长时间的加载和请求不停地分配和重新分配,并维持在较高水准的情况。
这种server GC 使用每个处理器一个堆、一个GC线程,并尽量的保持堆之间的平衡。在垃圾收集的时候,GC线程工作在各自的线程中,这样就最小化了锁资源,就保证了在这种应用条件下最有效的工作。
这种类型的GC只有在多处理器的机器上可见,如果你在单处理器上的设置这种模式,那你将得到实际运行的模式是非并发的workstation版本(Non Concurrent)。现在的双核也是这种模式,intel的超线程技术实现的cpu并不是真实的多cpu,因此它不会使用这种模式。
Asp.net 在多cpu的机器上默认使用这种模式,如果你想使用server GC模式,你可以在应用程序级别上做如下设置:
< /configuration>②Workstation GC – Concurrent
这种被用来作为winform应用程序和windows services 服务程序的默认设置。
这种模式是对交互的应用程序,这种程序要求应用程序不能暂停,即时一个相对很短暂的时间也是不行的。因为暂停进程会让用户界面闪烁或者当点击按钮的时候感觉应用程序没有响应。
这种实现方式是当进行Gen 2 收集的时候,将cpu和内存的使用量作为更短的停顿时间。
③Workstation GC – Non Concurrent
这种模式是模仿Server GC,只是收集是发生在引起GC的进程上,这种模式推荐为那种运行在单个cpu上的服务类型的应用程序。可以修改应用程序级上的配置来把 concurrency 关闭。
Concurrent WS
Non-Concurrent WS
Server GC
Design Goal
Balance throughput and responsiveness for client apps with UI
Maximize throughput on single-proc machines
Maximize throughput on MP machines for server apps that create multiple threads to handle the same types of requests
Number of heaps
1
1
1 per processor (HT aware)
GC threads
The thread which performs the allocation that triggers the GC
The thread which performs the allocation that triggers the GC
1 dedicated GC thread per processor
EE Suspension
EE is suspended much shorter but several times during a GC
EE is suspended during a GC
EE is suspended during a GC
Config setting
On a single proc
WS GC + non-concurrent
垃圾收集(garbage collection ,GC)的代价是什么,如何保证代价最小?
可以使用一些不同的计数器来衡量一个应用程序的GC的消耗。记住,所有这些计数器都是在收集结束之后更新的,这意味着你使用中会发现过了很长的一个不活动期,那些数据才可靠的。
① NET CLR Memory"% time in GC
这个计数器衡量GC花费的cpu时间的数量,计算方式是: GC时间/自上次GC后的cpu时间
② .NET CLR Memory"# Induced GC
这个是自有人调用GC.Collect()以来的垃圾收集的次数。完美情况下,应该是0,因为含有收集行为表示你花费了很多时间在GC上,而且因为GC不断的调整自己来适应收集模式,而手动的收集会使优化性能降低。
③ .NET CLR Memory"# Gen X collections
这个计数器显示了给定代的收集数量。因为Gen 2 的收集代价比Gen 1 和 Gen 0 要高很多,相对于 Gen1 , Gen 0 你想要更少的 Gen 2 的收集次数,Gen 2 : Gen 1 :Gen 0=1:10:100是比较理想的。
参考了原文: https://www.cnblogs.com/caoheyang911016/p/4108873.html