简述常见三种GC和四种GC回收器

1 常见的三种Garbage Collection

1.1 Minor GC

从年轻代空间(包括Eden和Survivor区域)回收内存被称为Minor GC :

  1. 当Eden区域满了,jvm无法为新对象分配内存,会触发Minor GC;
  2. 新生代好进行标记和复制操作,就不会存在内存碎片。
  3. 年轻代中指向永久代中的引用,在标记阶段就会忽略。
  4. stop-the-world。原因是Eden区中对象认为是垃圾,不会复制到Survivor区或者老年代。如果相反,Eden区
    大部分对象不符合GC 条件,那么 Minor GC指定的时间就比较长。每次Minor GC会清理年轻代的内存。
1.2 Major GC 和Full GC
  1. Major GC: 清理老年代
  2. Full GC: 清理整个堆内存,包括年轻代和老年代

但是更多情况下,许多Minor GC 会 触发Major GC ,所以实际情况两者分离是不可能的。这就使得我们关注重点变成,GC是否能并发处理这些GC.

2 四种垃圾回收器

2.1 串行Serial Collector 单线程回收

它是最古老的垃圾收集器,“Serial”体现在其收集工作是单线程的,并且在进行垃圾收集过程中,会进入臭名昭著的“Stop-The-World”状态。当然,其单线程设计也意味着精简的 GC 实现,无需维护复杂的数据结构,初始化也简单,所以一直是 Client 模式下 JVM 的默认选项。

  1. 从年代的角度,通常将其老年代实现单独称作 Serial Old,它采用了标记 - 整理(Mark-Compact)算法。清空整个Heap中的垃圾对象,清除元数据去已经被卸载的类信息,并进行压缩。
  2. 新生代的复制算法。Serial GC 的对应 JVM 参数是:-XX:+UseSerialGC
2.2 并行回收器(Paraller Collector ) :

又称throughput collector 。是jvm默认的回收器。Serial的升级版本,多线程进行GC。常见应用场景是配合老年代CMS GC工作参数如下:

-XX:+UseConcMarkSweepGC -XX:+UseParNewGC

根据Minor GC 和Full GC 的划分为

  1. ParNewGC : 通过-XX:+UseParNewGC参数来指定,多线程并行回收的Serial GC版本。
  2. ParallerGC:Server下默认的GC方式,通过-XX:+UseParallelGC参数指定,并发回收线程数可以用-XX:ParallelGCThreads指定。清除Heap中的部分垃圾对象,并进行部分空间压缩。
  3. ParallerOldGC:可以通过-XX:UseParallelOldGC参数指定。并发回收线程数用 -XX:ParallelGCThreads来指定。与ParallerGC的不同之处在于Full GC上,前者Full GC是将清空整个Heap中的垃圾对象,清除元数据去已经被卸载的类信息,并进行压缩。而后者是清除Heap中的部分垃圾对象,并进行部分空间压缩。
2.3 并发标记扫描收回器(CMS Collector) :

管理新生代方式和Paralel和Serial GC相同。而是在老年代中并发处理。尽量减少停顿时间。 XX:+USeParNewGC 打开并发标记扫描垃圾回收器。

2.3.1 基础
  1. CMS(Concurrent Mark Sweoop):并发标记清除回收器。管理新生代和Paraller和Serial回收器相同。而老年代尽可能并发执行,每个垃圾回收周期都有两次停顿。
  2. CMS 的目的就是为了消除Paraller和Serial回收器在Full GC 停顿周期时间长的问题。
2.3.2 CMS 的整个过程
  1. 初始标记(STW):通过一系列GCRoot标记直接可达的老年代对象;新生代引用老年代对象。(此过程在jdk8以前是单线程。8及以后就是多线程,通过CMSParallelInitialMarkEnabled可调),也就是说在此过程中新生代和老年代都会被扫描。
  2. 并发标记;经过上一次标记,开始tracing过程,标记所有可达对象。此过程是应用线程也在运行。
    由于应用在运行,这样会出现有些对象新、老代之间位置发生改变,对象引用也发生变换,那么怎么确定这些发生变化的对象呢?这些的变化都影响到老年代对象所在card被标记为dirty,为后面的重新标记做准备。
  3. 并发预清理
    标记老年代存活对象。老年代对象有可能是被并发阶段修改过的对象,所以说,也要扫描新生代。这样老年代和新生代都需要扫描,这个扫描优化是怎么处理的呢?应该有一个种,快速识别存活对象的一种机制。
    • 对于新生代来说:
      如果重新在来一个MinorGC 就好
      Eden的使用空间大于“CMSScheduleRemarkEdenSizeThreshold”,这个参数的默认值是2M;Eden的使用率大于等于“CMSScheduleRemarkEdenPenetration”,这个参数的默认值是50%。
      也就是说预清理后,eden空间使用超过2M时启动可中断的并发预清理(CMS-concurrent-abortable-preclean),直到eden空间使用率达到50%时中断,进入remark阶段。
      如果在可中断的时间内没有发生minor GC 怎么办呢?CMS有一个参数CMSMaxAbortablePrecleanTime默认是5s,也就是说5秒后,不管发没发生Minor GC,有没有到CMSScheduleRemardEdenPenetration都会中止此阶段,自动进入remark;
      那如果五秒内没有执行Minor GC,这个时候新生代还有好多活着对象,这就会使得STW变长。所以提供在重新标记之前强制Minor GC ,参数是CMSScavengeBeforeRemark。
    • 对于老年代来说:
      年代的机制就是CARD TABLE的东西(类似与一个数组);他将老年代划分成512byte的块。每一个card Table 对应一个块,如果对象引用发生变化就会变成dirty card;并发预清理阶段就会重新扫描该块,将该对象引用的对象标识为可达;
  4. 重新标记 (STW) 重新扫描堆中对象。扫描目的新生代的对象+GC Root+ 前面标记的dirty的card对应的块。多线程来处理这个过程。
  5. E:并发清除。用户线程激活,同时将没有标记存活对象标记为不可达;清理不可用不可达垃圾。在此过程中,用户线程依旧在运行,在此期间产生的新垃圾,在本次GC中没有办法清除。所以这些本次没办法清除的称之为:浮动垃圾。
    由于次清除的特性(CMS线程和用户线程都在运行):所以老年代的使用率没办法用到100%使用,CMS中有CMSInitiatingOccupancyFraction(默认92%)参数设置老年代空间使用百分比,达到百分比就进行垃圾回收。
  6. 并发重置:CMS 内部重置回收器,准备下一次
2.3.3 CMS的特性
  1. 优点:
    低延迟收集器,几乎没有停顿时间。只有在出示标记和并发标记的时候出短暂停顿。
  2. 缺点
    1. CMS中并发意味着多线程强占CPU的资源。
      CMS默认回收线程的公式:(CUP个数+3)/4 .这就意味着如果用户cup个数比较少的,CMS的CPU占用率就很高。显然这种情况以及用硬件打败,现在的机器都是多核处理。
    2. CMS收集老年代会出现内存碎片化现象
      不会对内存进行任何的压缩和整理,过多的碎片化内存会出现实际内存不足的情况,所以会出现Full GC的情况CMS 提供两个参数来完成Full GC
      ① UseCMSCompactAtFullCollection ,在进行Full GC 的过程中进行内存碎片的整理;
      ② CMSFullGCsBeforeCompaction,每隔多少次不压缩的Full GC ,执行一次压缩Full GC
    3. 出现浮动垃圾:在并发清除过程中,用户进程依然在运行,此时产生的垃圾是在本次清除过程中没办法清除。这部分垃圾被称为浮动垃圾。
2.3.5 关于永久代(元数据区的扩容)的垃圾回收

CMS默认情况是不会对永久代进行垃圾处理的。但是可以通过CMSPermGenSweepingEnabled参数来配置永久代的垃圾回收。开启后就会有一组后台线程针对永久代做收集(与触发老年代垃圾回收机制的指标是独立的)。

2.3.6 CMS的并发收集周期的触发问题

CMS的触发有两个条件:

  1. 阈值检查机制:由于并发清除过程会产生浮动垃圾。所以老年代的使用率没有办法达到100%。只能到达某一个阈值以后(jdk1.8默认值92%,1.6之后是92%,1.5默认是68%),或者通过CMSInitiatingOccupancyFraction和UseCMSInitiatingOccupancyOnly 两个参数来调节;过小会造成GC频繁;过大,导致并发模式失败。
  2. 动态检查机制:JVM会根据最近的回收历史,估算下一次老年代被耗尽的时间,快到这个时间了就启动一个并发周期。 可以用UseCMSInitiatingOccupancyOnly来将这个特性关闭。
2.4 G1 垃圾回收器

G1 (Garbage first ):是JDK7的新特性。jdk7以上都可以自主设置JVM GC类型。G1会将堆内存划分成相互独立的区块(默认1024),每一块都可能是不连续的 O(old区),Y(young区)区块(相对于CMS中O,Y区块是连续的)。G1会第一时间处理垃圾最多的区块。这个是garbage First的原因之一。
新生代的回收算法主要是复制算法,但是新生代的分区机制主要是为了便于调整区块大小的。

2.4.1 G1和CMS 相比突出的优势
  1. G1在压缩空间方面有优势

  2. G1通过将内存空间分成区域(Region)的方式避免内存碎片问题

  3. Eden、Survivor、Old区域不在固定,在内存使用率上更加灵活

  4. G1可以设计预期停顿时间(Pause Time)来控制垃圾回收时间避免引用雪崩的现象。

  5. G1在回收内存后会马上同时做合并空闲内存的工作、CMS默认情况是STW的时候合并内存。

  6. G1会在Young GC中使用、而CMS在O区中使用

2.4.2 关键词解释
  1. Regin
    G1默认把堆内存分为1024个分区,后续垃圾收集的单位都是以Region为单位的。Region是实现G1算法的基础,每个Region的大小相等,通过-XX:G1HeapRegionSize参数可以设置Region的大小(取值范围是2~32,且为2的指数)
    在这里插入图片描述

在上图中出现一些Region表明了H,代表Humongous,这些区域表示Region存储的是巨大对象(humongous Object ,H_obj) 大小大于等于Region一半的对象。
H_Obj有一下几个特征

  1. H_obj最直接分配到old gen,防止了反复拷贝移动;

  2. 在分配内存之前检查是否超过initiating heap occupancy percent(启动堆占用比例)和the marking threshold(标记阈值),如果超,会启动global concurrent marking,为的是提早回收,防止evacuation failures 和full GC.

  3. 为了减少连续额H_objs对象对GC的影响,需要把大对象变成普通的对象, 建议增大Region size。

  4. SATB (Snapchat-At-The_Beginning)GC开始时活对象的一个快照
    它是通过Root Tracing得到的,作用是维持并发GC的正确性,也是G1并发的基础。可以理解成GC开始之前堆内存的对象做一次快照。标记活着的对象。形成对象图。

    1. 如何维持正确性呢?根据三色标记算法(对象的三种存在状态)
      1. 白色:对象没有标记到,标记阶段结束后,会当做垃圾回收。
      2. 灰色:对象被标记了,但是它的field还没有标记或还没有标记完。
      3. 黑色:对象被标记了,且它的所有field也被标记完了。
    2. 并发标记的情况下,Mutator和Garbage Collector 线程同时修改对象。会出现白对象漏标的情况:
      1. Mutator赋予一个黑对象该白对象的引用 :白类 白对象 = 黑对象;
        在并发标记阶段,如果该白对象是new 出来的,并没有灰对象持有。 Region中有连个top-at-mark-start(TAMS)指针,分别是prevTAMS和nextTAMS.在TAMS以上的对象是新分配的,这是一种隐式标记,通过这种的方式找到了再GC过程中新分配的对象,并认为是活对象。
      2. Mutator删除了所有从灰对象到该白对象的直接或者间接引用
        如果灰对象到白对象的直接引用或间接引用被替换或者删除了,那白对象就会被漏标。从而导致被回收掉,这是非常严重的错误。为了防止这种现象的发生。G1给出了利用write barrier将就引用记录下来,以防止被清除(对象引用被替换是就会发生write barrier)。
      3. 副作用
        如果被替换的白对象就是要被收集的垃圾,那这次标记就会让它躲过这次GC,这就是Float Garbage(浮动垃圾)。因此SATB的做法精度比较低,所以造成Float Garbage比较多。
    3. Rset(Remembered Set)
      1. 简介
        辅助GC过程的一种结构,典型的空间获取时间的工具。每个Region都有一个Rset记录的是其他Region中的对象引用本Region对象的关系,属于points-into(谁引用了我的对象)(与之类似的有Card Table),而Card Table则是一种point-out(我引用了谁的对象)的结构, 每一个Card覆盖一定范围的Heap(一般是512Bytes)。
        在这里插入图片描述

      2. 实现过程
        G1的Rest是在Card Table的基础上实现的:每个Region会记录下别的Region指向自己的指针,并标记 这些指针的分别在那些Card的范围内。
        这个Rset其实是一个Hash Table ,key是别的Region的起始地址,Value是一个Card Table 的index的集合。

        上图中有三个Region,每个Region被分成了多个Card,在不同Region中的Card会相互引用,Region1中的Card中的对象引用了Region2中的Card中的对象,蓝色实线表示的就是points-out的关系,而在Region2的RSet中,记录了Region1的Card,即红色虚线表示的关系,这就是points-into。 而维系RSet中的引用关系靠post-write barrier和Concurrent refinement threads来维护,操作伪代码如下:

        void oop_field_store(oop* field, oop new_value) {
            pre_write_barrier(field);             // pre-write barrier: for maintaining SATB invariant
            *field = new_value;                   // the actual store
            post_write_barrier(field, new_value); // post-write barrier: for tracking cross-region reference
         }
        

        post-write barrier记录了跨Region的引用更新,更新日志缓冲区则记录了那些包含更新引用的Cards。一旦缓冲区满了,Post-write barrier就停止服务了,会由Concurrent refinement threads处理这些缓冲区日志。

      3. RSet究竟是怎么辅助GC的呢?
        在做YGC的时候,只需要选定young generation region的RSet作为根集,这些RSet记录了old->young的跨代引用,避免了扫描整个old generation。
        而mixed gc的时候,old generation中记录了old->old的RSet,young->old的引用由扫描全部young generation region得到,这样也不用扫描全部old generation region。所以RSet的引入大大减少了GC的工作量

  5. 类似结构
    还有一种数据结构是辅助GC:Collection Set(Cset) Cset主要记录GC要收集的Region集合,集合中的Region是任意代的。 在GC的时候,对old->young和old->old的跨代对象引用,只要扫描对应的Cset中的RSet即可。

  6. 停顿预测模型(Pause Prediction Model)
    G1使用暂停预测模型来满足用户定义的暂停时间目标,并根据指定的暂停时间目标选择要收集的区域数量。
    G1 是一个相应时间优先的GC算法,它与CMS最大的不同是:用户可以设定整个GC过程的期望停顿时间。参数-XX:XX:MaxGCPauseMillis指定一个G1收集过程目标期望停顿时间,默认值200ms。G1根据整个模型统计计算出的历史数据来预测本次收集需要Region数量,从而满足用户设定 的目标停顿时间。停顿预测模型是以衰减标准偏差为理论基础实现的。

2.4.3 GC过程
2.4.3.1 G1 GC 模式

G1 提供了两种GC模式,Young GC和Mixed GC,这两种都是完全的STW的。

  1. Young GC:选定年轻代里的Region,通过控制年轻代的Region个数,即年轻代内存的发小,来控制Young GC的时间开销。
  2. Mixed GC:选定年轻代里面的Region,外加根据global concurrent marking统计出收集收益高的若干老年代Region。

在用户指定的开销目标范围内尽可能选择收益高的老年代Region。

由上面可知,Mixed GC不是Full GC ,它只能部分回收老年代的Region,如果Mixed GC实在无法跟上程序分配内存速度,导致老年代填满无法继续进行Mixed GC ,那就会使用Serial Old GC(Full GC)来收集整个GC Heap。而G1不提供Full GC。
G1的运行过程是这样的,会在Young GC和Mix GC之间不断的切换运行,同时定期的做全局并发标记,在实在赶不上回收速度的情况下使用Serial old GC。
初始标记是搭在YoungGC上执行的,在进行全局并发标记的时候不会做Mix GC,在做Mix GC的时候也不会启动初始标记阶段。
当MixGC赶不上对象产生的速度的时候就退化成Serial old GC,这一点是需要重点调优的地方。

2.4.3.2 过程简述

G1垃圾回收分为两个阶段:全局并发标记和拷贝存活对象阶段。

  1. 全局并发标记(global concurrent marking)
    前面多次提到global concurrent marking(全局并发标记),它的执行过程有点类似CMS,但是不同的是,在G1 GC中,它主要是为了Mixed GC提供服务的,并不是一次GC过程的一个必须环节。

    1. 初始标记(initial mark ,STW):标记了从GC Root开始直接可达的对象。
    2. 并发标记(Concurrent Marking):GC Root 开始对heap的对象标记,标记线程与应用程序线程并发执行,并收集各个Region的存活对象信息。同时还会扫描SATB write barrier所有记录下的引用。
    3. 最终标记(Remark ,STW)标记那些在并发标记阶段发生变化的对象,将被回收。
    4. 清除垃圾(Cleanup):清除空Region(没有存活对象的),region加入到空闲列表中。这个阶段并不会实际上去做垃圾的收集,只是去根据停顿模型来预测出CSet,等待evacuation阶段来回收。
  2. 拷贝存活对象阶段(Evacuation)
    Evacuation阶段是全暂停的。该阶段把一部分Region里的活对象拷贝到另一部分Region中,从而实现垃圾的回收清理。
    Evacuation阶段从第一阶段选出来的Region中筛选出任意多个Region作为垃圾收集的目标,这些要收集的Region叫CSet,通过RSet实现。筛选出CSet之后,G1将并行的将这些Region里的存活对象拷贝到其他Region中,这点类似于ParalledScavenge的拷贝过程,整个过程是完全暂停的。关于停顿时间的控制,就是通过选择CSet的数量来达到控制时间长短的目标。

  3. G1最佳实践
    在使用G1垃圾收集器的时候遵循以下实践可以少走不少弯路:

    1. 不断调优暂停时间指标
      通过XX:MaxGCPauseMillis=x可以设置启动应用程序暂停的时间,G1在运行的时候会根据这个参数选择CSet来满足响应时间的设置。
      一般情况下这个值设置到100ms或者200ms都是可以的(不同情况下会不一样),但如果设置成50ms就不太合理。暂停时间设置的太短,就会导致出现G1跟不上垃圾产生的速度。最终退化成Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。
    2. 不要设置新生代和老年代的大小
      G1收集器在运行的时候会调整新生代和老年代的大小。通过改变代的大小来调整对象晋升的速度以及晋升年龄,从而达到我们为收集器设置的暂停时间目标。
      设置了新生代大小相当于放弃了G1为我们做的自动调优。我们需要做的只是设置整个堆内存的大小,剩下的交给G1自己去分配各个代的大小。
    3. 关注Evacuation Failure
      Evacuation Failure类似于CMS里面的晋升失败,堆空间的垃圾太多导致无法完成Region之间的拷贝,于是不得不退化成Full GC来做一次全局范围内的垃圾收集。

参考链接:
https://tech.meituan.com/2016/09/23/g1.html
https://www.cnblogs.com/yunxitalk/p/8987318.html

<think>我们参考了引用[1][2][3][5]中关于C#垃圾回收机制的描述,特别是引用[1][3]提供了较为直接的概述。根据这些信息,我们可以将C#垃圾回收机制概述如下: C#的垃圾回收(Garbage Collection, GC)机制是.NET框架提供的自动内存管理服务,它负责跟踪应用程序中不再使用的对象,并自动释放这些对象占用的内存。垃圾回收器通过定期扫描堆内存来找出不再被引用的对象,并将它们标记为可回收的,最终释放这些对象的内存[^1][^3]。 ### 垃圾回收机制概述 1. **目的与重要性**: - 简化内存管理:开发者无需手动分配释放内存,降低了内存泄漏悬空指针的风险[^3]。 - 避免内存泄漏:自动回收不再使用的对象,确保应用程序长期运行的稳定性[^3]。 - 提高程序性能:通过高效的内存回收策略,优化内存使用,提升程序整体性能[^3]。 2. **工作原理**: - **跟踪对象引用**:垃圾回收器从根对象(如全局变量、活动线程栈中的局部变量等)出发,递归遍历所有可达对象,形成“可达对象图”[^5]。 - **标记阶段**:将可达对象标记为“存活”,未被标记的对象视为垃圾[^3][^5]。 - **回收阶段**:释放垃圾对象占用的内存(引用[3]提到采用标记-清除、复制等算法)。 - **压缩阶段(可选)**:移动存活对象使内存连续排列,解决内存碎片问题(引用[5]详细描述了这一过程)[^5]。 3. **执行流程**: - **挂起线程**:为避免回收过程中对象状态变化,GC执行时会暂停所有托管线程[^5]。 - **确定根对象**:识别所有全局活动局部变量[^5]。 - **创建可达对象图**:遍历根对象引用的所有对象链[^5]。 - **回收与压缩**:释放垃圾内存,整理剩余对象以减少碎片[^5]。 - **指针修复**:因对象移动需更新引用地址[^5]。 4. **算法与策略**: - **分代收集**:.NET GC将堆分为三代(Gen0/Gen1/Gen2),新对象在Gen0,经历多次回收仍存活则升级到更高代。这种设计优化了回收效率,因为新对象更易成为垃圾[^1][^2]。 - **触发条件**:当Gen0满、系统内存不足或显式调用`GC.Collect()`时触发回收[^4]。 ### 优缺点 - **优点**: - 降低内存管理复杂度[^3]。 - 减少内存泄漏风险[^3]。 - **缺点**: - 回收过程会暂停应用程序(Stop-the-World),可能影响实时性[^3]。 - 频繁回收(如强制调用`GC.Collect()`)可能导致性能下降[^4]。 ### 示例:强制垃圾回收(不推荐) ```csharp // 强制触发垃圾回收(通常应避免) for (int i = 0; i < 90000; i++) { string str = i.ToString(); GC.Collect(); // 显式调用GC,但会破坏GC的自动优化 } ``` > **注意**:显式调用`GC.Collect()`会干扰GC的自动调度机制,通常仅用于调试或特殊场景[^4]。 ### 总结 C#垃圾回收机制通过自动化内存管理,显著提升了开发效率程序健壮性。其核心是分代收集与标记-压缩算法,辅以智能的触发策略,在内存效率与性能开销间取得平衡[^1][^3][^5]。 §§相关问题§§ 1. .NET中分代垃圾回收的具体流程是怎样的? 2. 如何避免垃圾回收导致的应用程序暂停(Stop-the-World)问题? 3. 在C#中显式调用`GC.Collect()`有哪些利弊?</think>### C# 垃圾回收机制概述 C# 的垃圾回收(Garbage Collection, GC)是 .NET 运行时提供的**自动化内存管理机制**,它通过跟踪对象引用关系,自动回收不再使用的内存资源。其核心目标是**简化内存管理**、**避免内存泄漏**并**提升程序健壮性**。以下是关键特性概述: --- #### 1. **核心工作原理** [^1][^3][^5] - **引用跟踪**:GC 从根对象(如全局变量、活动线程栈中的变量)出发,递归扫描所有可达对象。 - **标记阶段**:标记所有被引用的对象为“存活”(不可回收)。 - **回收阶段**:清除未标记的对象(垃圾),释放其内存。 - **压缩阶段**(可选):移动存活对象使内存连续排列,消除碎片(如图示): ```mermaid graph LR A[碎片化内存] --> B[回收垃圾对象] B --> C[移动存活对象] C --> D[连续内存空间] ``` #### 2. **执行流程** [^5] 1. **挂起线程**:暂停所有托管线程以保证一致性。 2. **确定根对象**:识别全局/局部变量等起点。 3. **构建可达对象图**:遍历对象引用链。 4. **回收与压缩**:释放垃圾内存并整理空间。 5. **指针修复**:更新对象移动后的引用地址。 #### 3. **核心算法** [^2][^3] - **分代收集**(核心策略): - **Gen 0**:新创建对象(回收频率高)。 - **Gen 1**:Gen 0 存活对象(中等频率回收)。 - **Gen 2**:长期存活对象(低频回收)。 ```csharp // 示例:对象代际提升 var obj = new MyClass(); // 初始在 Gen 0 GC.KeepAlive(obj); // 若存活,可能升至 Gen 1/2 ``` - **标记-清除**:识别并回收不可达对象。 - **复制算法**:将存活对象复制到新内存区(用于 Gen 0)。 - **标记-整理**:移动对象消除碎片(用于 Gen 1/2)。 #### 4. **触发条件** [^1][^4] - **自动触发**: - Gen 0 内存满时(最常见)。 - 系统物理内存不足。 - 应用程序转入后台(如移动端)。 - **手动触发**(不推荐): ```csharp GC.Collect(); // 强制回收(可能破坏性能优化) ``` #### 5. **优势与局限** [^3] | **优势** | **局限** | |----------|----------| | 避免内存泄漏 | 回收时暂停线程(Stop-the-World) | | 简化开发复杂度 | 无法管理非托管资源(需 `IDisposable`) | | 优化内存碎片 | 频繁回收可能影响性能 | | 自适应内存分配 | 长生命周期对象回收延迟 | --- ### 总结 C# 的 GC 通过**分代收集****自动内存回收**,实现了高效的内存管理。开发者无需手动释放对象,但需注意非托管资源(如文件句柄)需通过 `Dispose()` 显式释放[^3]。GC 线程在后台周期性运行,平衡内存使用与程序性能[^1][^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值