因此,我正在阅读一个有关强制C#垃圾收集器在几乎每个答案都相同的地方运行的问题:您可以做到,但您不应该这样做-除了极少数情况下。可悲的是,那里没有人详细说明这种情况。

您能告诉我在哪种情况下强制垃圾回收实际上是一个好主意吗?我不是在要求C#的特定情况,而是要求所有具有垃圾收集器的编程语言。我知道您不能在所有语言(例如Java)上强制使用GC,但让我们假设您可以。

评论

“但是,所有具有垃圾收集器的编程语言”不同的语言(或更恰当地说,是不同的实现)使用不同的方法进行垃圾收集,因此您不太可能找到一种“一刀切”的规则。 />
@Doval如果您处于实时限制之下,并且GC无法提供匹配的保证,那么您就处于困境与困境之间。与不执行任何操作相比,它可以减少不必要的暂停,但是据我所知,避免在正常操作过程中进行分配是“更轻松的”。
我给人的印象是,如果您期望实时的截止日期很长,那么您永远不会使用GC语言。

我看不到如何以非特定于VM的方式回答此问题。与32位进程相关,与64位进程无关。 .NET JVM和高端版本

@DavidConrad,您可以在C#中强制使用。因此就是问题。

#1 楼

您确实无法就使用所有GC实现的适当方式发表笼统的声明。他们千差万别。因此,我将与您最初提到的.NET进行交谈。 >关于收集的唯一建议是:永远不要做。

如果您真正了解GC的复杂细节,则不需要我的建议,所以没关系。如果您还没有百分百的信心,它会有所帮助,并且必须上网查找以下答案:您不应该致电GC.Collect,或者:您应该学习有关GC如何工作的详细信息由内而外,只有这样,您才知道答案。可用于对事物的时间进行概要分析的工具。知道第二种算法导致结果不正确时,您可以立即分析一种算法,收集并分析另一种算法。

这种分析是我一次曾经建议向任何人手动收集。对象堆将直接进入第2代,尽管第2代同样适用于寿命长的对象,因为它的收集频率较低。如果您知道出于任何原因将短命的对象加载到Gen 2中,则可以更快地清除它们,以使Gen 2变小并加快收集速度。

这是我能想到的最好的例子,而且不好-您在这里建立的LOH压力会导致更频繁的收集,并且收集是如此频繁-可能会像清除LOH一样当您用临时物体将其吹灭时,速度很快。我只是不相信自己会假设比GC更好的收集频率-由比我聪明得多的人进行调整。


所以让我们来谈谈其中的一些语义和机制.NET GC ...或..

关于.NET GC的所有知识,我想我都知道。众所周知,许多GC都是黑魔法,尽管我尝试遗漏了不确定的细节,但我可能还是有些错误。当然,以及我根本不了解的大量信息。使用此信息需要您自担风险。


GC概念

.NET GC发生在不一致的时间,这就是为什么将其称为“不确定的”,这意味着您不能依赖它在特定时间发生。这也是一个世代垃圾收集器,这意味着它将对象划分为它们经历了多少次GC传递。

Gen 0堆中的对象已经经历了0个集合,这些是最近才新创建的自实例化以来未发生任何收集。 Gen 1堆中的对象已通过一次收集过程,同样,Gen 2堆中的对象已通过了两次收集过程。

现在,值得注意的是它可以对这些特定世代和分区进行限定的原因。 .NET GC仅识别这三代,因为遍历这三个堆的收集传递都略有不同。有些对象可能在数千次收集收集中幸存下来。 GC只是将它们留在了Gen 2堆分区的另一侧,没有必要将它们进一步分区,因为它们实际上是Gen 44。传递给它们的集合与Gen 2堆中的所有传递都是相同的。


集合中的内容

GC集合传递的基本概念是,它检查堆空间中的每个对象以查看是否仍然存在这些对象的实时引用(GC根)。如果为某个对象找到了GC根目录,则意味着当前仍可以访问并执行该对象的代码,因此无法将其删除。但是,如果找不到某个对象的GC根目录,则意味着正在运行的进程不再需要该对象,因此可以将其删除以为新对象释放内存。

现在完成清理后一堆物体并留下一些物体,这将是一个不幸的副作用:已移除死物体的活动物体之间的自由空间间隙。如果仅留下这种内存碎片,只会浪费内存,因此集合通常会执行所谓的“压缩”,将所有剩余的活动对象取出并紧紧压在堆中,这样对于Gen来说,空闲内存在堆的一侧是连续的0.

现在有了3个内存堆的概念,所有内存都按它们经历的收集遍数进行了分区,下面我们来讨论为什么存在这些分区。
第0代收藏集

Gen 0是绝对最新的对象,往往很小-因此您可以安全地频繁收集它。频率确保堆保持很小,并且收集非常快,因为它们是在如此小的堆上进行收集。这或多或少地基于一种启发式的主张:您创建的绝大多数临时对象是非常临时的,因此临时的对象将几乎不再在使用后立即使用或引用,因此可以被收集。


第1代收藏集

第1代是那些没有属于此类临时对象的对象,可能寿命很短,因为-创建的对象的一部分不会长时间使用。因此,第1代也相当频繁地进行收集,再次使其堆很小,因此收集速度很快。但是,假设它的对象是临时对象,而不是Gen 0,因此它的收集频率少于Gen0。

我要坦率地说,我不知道Gen 0的收集通行证和第1代,如果除了它们收集的频率之外还有其他任何东西。


第2代收藏集

第2代现在必须是所有堆的母体?好吧,是的,这或多或少是对的。它是所有永久对象的居住地-例如Main()所居住的对象,以及Main()引用的所有对象,因为这些对象将一直植根直到Main()在过程结束时返回。鉴于第二代基本上是其他世代无法收集的所有东西的存储桶,因此它的对象在很大程度上是永久性的,或者至少是长期存在的。因此,认识到第二代中几乎没有什么东西实际上可以收集,不需要经常收集。这使得它的收集也变慢,因为它执行的频率要少得多。因此,基本上,这是他们在奇怪的情况下处理所有额外行为的地方,因为他们有时间执行它们。第2代额外行为的一个示例是,它还在大型对象堆上进行收集。到目前为止,我一直在谈论小型对象堆,但是由于上面我所说的压缩,.NET运行时将某些大小的东西分配到一个单独的堆中。压缩需要在小型对象堆上完成收集时移动对象。如果第1代中有一个10mb的活物,则收集之后完成压缩将花费更长的时间,从而减慢了第1代的收集速度。因此,将10mb对象分配给大对象堆,并在第2代期间不频繁运行。您将终结器放在引用了.NETs GC范围之外的资源(非托管资源)的对象上。终结器是GC要求收集非托管资源的唯一方法-您实现终结器以手动收集/删除/释放非托管资源以确保它不会从您的流程中泄漏。当GC开始执行对象终结器时,您的实现将清除非托管资源,使GC能够删除您的对象而不会冒资源泄漏的风险。

终结器执行此操作的机制是直接在终结队列中引用。当运行时使用终结器分配对象时,它会将指向该对象的指针添加到终结队列中,并将对象锁定在适当的位置(称为固定),因此压缩不会移动它,这会破坏终结队列引用。随着收集过程的进行,最终将发现您的对象不再具有GC根目录,但是必须执行终结处理才能收集对象。因此,当对象失效时,集合会将其引用从终结队列中移出,并将对其的引用放置在所谓的“可扩展”队列中。然后收集继续进行。在将来的另一个“不确定性”时间,称为终结器线程的单独线程将通过FReachable队列,为每个引用的对象执行终结器。完成后,FReachable队列为空,它在每个对象的标头上翻转了一点,表示它们不需要终结(也可以使用GC.SuppressFinalize手动翻转,这在Dispose()方法中很常见),怀疑它已经松开了物件,但不要在此引用我。该对象所在的任何堆上出现的下一个集合最终将收集它。 Gen 0集合甚至不关注那些需要完成终结的对象,它会自动升级它们,甚至不检查其根。在Gen 1中需要终结的无根对象将被丢入FReachable队列,但是集合对它没有做任何其他事情,因此它进入Gen 2。 t GC.SuppressFinalize将在第2代中收集。

评论


@FlorianMargaine是的...在所有实现中都说“ GC”确实没有道理。

–吉米·霍法(Jimmy Hoffa)
15年3月18日在0:35

tl; dr:改为使用对象池。

–罗伯特·哈维(Robert Harvey)
15年3月18日在4:31

tl; dr:对于定时/性能分析,它可能很有用。

–kutschkem
15年3月18日在8:08

在阅读完我上面对力学的描述(据我了解)之后,@ Den,您看到的好处是什么?您清理了许多对象-在SOH(或LOH?)中?您是否只是让其他线程暂停此集合?该收集是否仅将清除的第二代对象提升了两倍?收集是否导致LOH压缩(已将其打开?)?您有多少个GC堆,并且GC是在服务器还是台式机模式下? GC是一个冰冷的冰山,奸诈在海底。只是避开。我不够聪明,无法舒适地收集东西。

–吉米·霍法(Jimmy Hoffa)
2015年3月18日14:58



@RobertHarvey对象池也不是灵丹妙药。垃圾收集器的第0代实际上已经是一个对象池-通常将其大小设置为适合最小的缓存级别,因此通常在已经在缓存中的内存中创建新对象。您的对象池现在正在与GC的托儿所争夺缓存,如果GC的托儿所和您的池的总和大于缓存,那么您显然会丢失缓存。而且,如果您现在计划使用并行性,则必须重新实现同步,并担心错误共享。

–Doval
15年3月18日在19:50

#2 楼


可悲的是,那里没有人详细说明这种情况。


我举一些例子。总而言之,强制使用GC是个好主意,但这是完全值得的。这个答案是根据我对.NET和GC文献的经验得出的。它应该可以很好地推广到其他平台(至少具有重要GC的平台)。



各种基准。您希望基准测试开始时有一个已知的托管堆状态,以便GC在基准测试期间不会随机触发。重复执行基准测试时,您希望每次重复执行相同数量和数量的GC。

突然释放资源。例如,关闭重要的GUI窗口或刷新缓存(从而释放旧的可能很大的缓存内容)。 GC无法检测到该错误,因为您所做的只是将引用设置为null。它孤立了整个对象图的事实不易检测。
释放泄漏的非托管资源。当然,这永远都不会发生,但是我已经看到了第三方库泄漏内容(例如COM对象)的情况。开发人员有时会被迫进行收藏。

交互式应用程序,例如游戏。在进行游戏时,每帧的时间预算非常严格(60Hz => 16ms每帧)。为了避免出现问题,您需要一种应对GC的策略。一种这样的策略是尽可能延迟G2 GC,并在适当的时间(例如加载屏幕或剪辑场景)强制执行。 GC无法确定最佳时刻是什么时候。

一般来说,延迟控制。某些Web应用程序会禁用GC,并在从负载均衡器旋转中退出时定期运行G2收集。这样,G2延迟就不会对用户浮出水面。

如果您的目标是吞吐量,那么GC越稀有越好。在这些情况下,强制进行收集不会产生积极的影响(人为造成的问题除外,例如通过删除散布在活动对象中的无效对象来提高CPU缓存利用率)。批量收集对于我认识的所有收集器来说效率更高。对于稳定状态下的生产应用程序,诱导GC不会消耗内存。在那些情况下,诱导GC可能是有道理的。我所知道的任何GC都不是那么复杂,对于GC来说,要实现最佳性能确实很难。 GC比开发人员了解的少。它的启发式方法基于内存计数器以及诸如收集率之类的东西。启发式方法通常很好,但是它们不能捕获应用程序行为的突然变化,例如释放大量托管内存。它也对非托管资源和等待时间要求视而不见。在很小的堆上,成本可能很小。我已经在具有1GB堆大小的生产应用程序上看到了NETGB 4.5的1-2GB /秒的G2收集速率。

评论


对于延迟控制的情况,我想不是定期执行此操作,您也可以根据需要(即,当内存使用量超过特定阈值时)执行此操作。

–PaŭloEbermann
15年3月19日在19:34

倒数第二段为+1。有些人对编译器有相同的看法,并很快将几乎所有内容称为“过早优化”。我通常会告诉他们类似的事情。

– Honza Brabec
15年3月20日在9:19

对该段落也+1。我感到震惊的是,人们认为别人编写的计算机程序必须比自己更好地理解其程序的性能特征。

–user541686
15年3月21日在19:05

@HonzaBrabec在两种情况下,问题都是相同的:如果您认为自己比GC或编译器了解得更多,那么很容易伤害自己。如果您实际上了解更多,那么只有在知道还不成熟时才进行优化。

– svick
2015年5月3日21:13

#3 楼

作为一般原则,垃圾收集器会在遇到“内存压力”时进行收集,因此最好不要在其他时间收集垃圾,因为这可能会导致性能问题,甚至在程序执行中出现明显的暂停。而且实际上,第一点取决于第二点:对于分代垃圾收集器而言,至少,垃圾与良好对象的比率越高,它的运行效率就越高,因此,为了使暂停程序所花费的时间最少,它必须拖延并让垃圾尽可能多地堆积起来。已造成大量垃圾,并且2)用户期望花费一些时间并使系统始终无响应。一个经典的例子是在最后加载大的东西(文档,模型,新关等)。

#4 楼

没有人提到的一件事是,尽管Windows GC非常出色,但是Xbox上的GC却是垃圾(双关语)。对于及时收集垃圾至关重要,以免出现意外,否则您将遇到间歇性的FPS间歇性故障。此外,在XBox上,通常使用struct s方法(比平时更常见)来最大程度地减少需要进行垃圾收集的对象的数量。

#5 楼

垃圾收集首先是内存管理工具。这样,垃圾收集器将在内存不足时进行收集。即使您今天可以进行改进,很可能未来对所选垃圾收集器的改进也会使您的优化无效或什至适得其反。记忆。在垃圾收集的环境中,大多数有价值的非内存资源都具有close方法或类似方法,但是在某些情况下由于某些原因(例如与现有API的兼容性)并非如此。当您知道正在使用有价值的非内存资源时,手动调用垃圾回收可能会很有意义。
RMI
一个具体的例子是Java的远程方法调用。 RMI是一个远程过程调用库。通常,您有一台服务器,该服务器使客户端可以使用各种对象。如果服务器知道某个对象没有被任何客户端使用,则该对象有资格进行垃圾回收。
但是,服务器知道这一点的唯一方法是客户端告诉它,而客户端只能告诉服务器,一旦客户端使用了垃圾回收,无论客户端使用什么对象,它都不再需要对象。
这带来了一个问题,因为客户端可能有很多可用内存,因此可能不会非常运行垃圾回收经常。同时,服务器可能在内存中有很多未使用的对象,由于不知道客户端没有使用它们而无法收集。
RMI中的解决方案是让客户端运行垃圾收集定期(即使它有很多可用内存),以确保在服务器上及时收集对象。

评论


“在这些情况下,当您知道正在使用有价值的非内存资源时,手动调用垃圾回收可能是有意义的” –如果正在使用非内存资源,则应使用using块,否则应调用Close确保尽快丢弃资源的方法。依靠GC清理非内存资源是不可靠的,并且会引起各种问题(尤其是需要锁定才能访问的文件,因此只能打开一次)。

–法律
18年1月29日在18:34

并且如答案中所述,当可以使用close方法(或资源可以与using块一起使用)时,这是正确的方法。答案专门针对这些机制不可用的罕见情况。

–James_pic
18年1月30日在18:42

我个人的观点是,任何管理非内存资源但不提供关闭方法的接口都是不应使用的接口,因为无法可靠地使用它。

–法律
18年1月31日在9:56

@Jules我同意,但有时这是不可避免的。有时抽象泄漏,使用泄漏抽象比不使用抽象更好。有时您需要使用遗留代码,这些遗留代码要求您做出承诺,知道自己无法遵守。是的,这种情况很少见,如果可能的话应该避免使用,这是有原因的,因为所有这些警告都在强迫垃圾收集,但是确实出现了这些情况,OP正在询问这些情况是什么样的-我已经回答了。

–James_pic
18年1月31日在10:44

#6 楼

最佳做法是在大多数情况下不要强制垃圾回收。 (我研究过的每个系统都强制执行垃圾收集,强调了一些问题,如果解决了这些问题,将消除了强制执行垃圾收集的需要,并大大加快了系统的运行速度。)如果您对内存使用情况了解更多,那么垃圾收集器就会知道。在多用户应用程序或一次响应一个以上请求的服务中,这不太可能是正确的。例如。考虑一个应用程序。


在命令行上给出了文件名列表
处理单个文件,然后将结果写到结果文件中。在处理文件时,会创建很多互连的对象,这些对象在文件处理完成之前无法收集(例如,解析树)。

您可能可以进行案例分析(经过仔细的测试),在处理完每个文件后应强制执行完整的垃圾回收。

另一种情况是,该服务每隔几分钟醒来以处理一些项目,并且在睡眠时不保持任何状态。然后在睡觉前强制收集一个完整的集合可能是值得的。


我唯一考虑强制收集的一次是当我知道很多东西时对象的创建是最近创建的
,目前仅引用了
很少的对象。类型的东西,而不必强加我自己的GC。

#7 楼

在某些情况下,您可能想自己调用gc()。一件好事。但是,并非总是存在总是可以提升的对象。肯定有可能在此gc()调用之后,剩下的对象很少,更不用说将其移入较旧的空间了。您只想清除尽可能多的空间即可进行准备。这只是常识。通过手动调用gc(),将不会对要加载到内存中的大量对象中的一部分进行多余的参考图检查。简而言之,如果您在将大量内存加载到内存之前先运行gc(),则在加载过程中引发的gc()至少在加载时开始产生内存压力的时间就会减少一次。
完成大型对象的加载而且您不太可能将更多对象加载到内存中。简而言之,您从创建阶段转到使用阶段。通过根据实现调用gc(),将压缩所使用的内存,这将大大提高缓存的局部性。这将导致性能的大幅提高,这是您无法从性能分析中获得的。物理内存。这又使新的大型对象集合更加连续和紧凑,从而提高了性能


评论


有人可以指出拒绝投票的原因吗?我自己对判断答案的知识还不够了解(乍一看,这对我来说很有意义)。

–欧米茄
15年3月18日在4:12

我猜你对第三点持反对态度。也可能会说“这只是常识”。

–user253751
15年3月18日在4:40

创建对象的大集合时,GC应该足够聪明,以知道是否需要一个集合。需要压缩内存时相同。依靠GC优化相关对象的内存局部性似乎是不可能的。我认为您可以找到其他解决方案(结构,不安全等)。 (我不是拒绝投票的人)。

–纪尧姆
2015年3月18日在10:27



在我看来,您第一个美好时光的想法只是个坏建议。最近有一次收集的可能性很高,因此您再次尝试进行收集的目的只是将对象任意地提升给后代,这几乎总是不好的。下一代的集合开始时需要更长的时间,增加它们的堆大小“以清除尽可能多的空间”只会使问题变得更加棘手。另外,如果您要增加负载的内存压力,则无论如何都可能会引发集合,由于Gen1 / 2的增加,集合会运行得更慢

–吉米·霍法(Jimmy Hoffa)
15年3月18日在14:48

通过根据实现调用gc(),将压缩所使用的内存,这将大大提高缓存的局部性。这将大大提高性能,而性能是无法从性能分析中获得的。如果您连续分配大量对象,则它们已经被压缩。如果有的话,垃圾回收可能会使它们稍微混乱一些。无论哪种方式,使用密集且不会在内存中随机跳动的数据结构都会产生更大的影响。如果您使用的是天真的每个节点一个元素的链表,那么没有多少手动的GC技巧可以弥补这一点。

–Doval
15年3月19日在1:23

#8 楼

一个真实的例子:

我有一个Web应用程序,该应用程序使用了非常少的大量数据集,这些数据很少更改并且需要非常快速地访问(对于通过AJAX进行的每次击键响应足够快) )。

在这里要做的显而易见的事情是将相关图加载到内存中,然后从那里而不是数据库中访问它,并在DB更改时更新图。 />但非常大,由于未来的增长,单纯的负载将占用至少6GB的内存。 (我没有确切的数字,一旦很明显我的2GB机器试图处理至少6GB的数据,我便拥有了所需的所有测量值,以知道它无法正常工作。)幸运的是,在这组数据中有大量的冰棒不可改变的对象彼此相同;一旦确定某个批次与另一个批次相同,就可以将一个引用别名化为另一个引用,从而允许收集大量数据,从而将所有内容放入不到半个演出中。 />一切顺利,但是为此仍然需要大约半分钟的时间来遍历6GB以上的对象,以达到此状态。任由GC自己应付,

,因此在此构建过程中定期调用GC.Collect()意味着整个过程顺利进行。当然,在应用程序运行的其余时间中,我并没有手动调用GC.Collect()。 br />
使用相对较少的情况下有很多对象可供收集(提供了价值兆字节的值,这种图的构建是整个应用程序生命周期中非常罕见的情况(大约一分钟)每周)。
在性能损失相对可以容忍的情况下执行此操作;这仅在应用程序启动时发生。 (此规则的另一个很好的例子是在游戏中的关卡之间,或游戏中的其他点之间,玩家不会因暂停而感到不适)。
请确保配置确实有改善。 (非常容易;“有效”几乎总是比“无效”击败)。在应用1和2时,第3点表明它会使情况变得更糟,或者至少使情况变得不那么好(并且几乎没有改善,我倾向于不调用而不是调用,因为这种方法在应用程序的生命周期中更有可能证明更好) 。

#9 楼

我使用的垃圾处理有些不合常规。

不幸的是,这种误导的做法在C#世界中非常普遍,它使用丑陋,笨拙,不雅且易于出错的方式实现对象处置。这个成语称为IDisposable-dispose。 MSDN详细描述了它,许多人对此发誓,认真地遵循它,花费数小时不间断地讨论应该如何做,等等。不是对象处置模式本身;我所说的丑陋是特定的IDisposable.Dispose( bool disposing )成语。垃圾收集器来清理资源,因此人们可以在IDisposable.Dispose()内执行资源清理,以防万一他们忘记了,他们还可以在析构函数内进行更多尝试。要知道,以防万一。在那个时间点垃圾收集器已经照顾好了,因此需要一个单独的IDisposable.Dispose()方法,该方法接受一个IDisposable.Dispose()标志来知道是否应该清除托管对象和非托管对象,或者只清理非托管对象。 />
对不起,但这太疯狂了。

我遵循爱因斯坦的公理,它说事情应该尽可能简单,但不要简单。显然,我们不能忽略清理资源,因此,最简单的解决方案至少必须包括这一点。下一个最简单的解决方案是始终在应该处置的准确时间处置所有事物,而不必依靠析构函数作为替代方法而使事情复杂化。

现在,严格来说,当然不可能保证没有程序员会犯忘记忘记调用Dispose()的错误,但是我们可以做的是使用析构函数来捕获此错误。这确实非常简单:如果析构函数检测到一次性对象的bool disposing标志从未设置为IDisposable.Dispose(),那么所有析构函数都将生成一个日志条目。因此,使用析构函数不是我们处置策略的必要组成部分,而是我们的质量保证机制。并且由于这是仅调试模式的测试,因此我们可以将整个析构函数放置在disposed块中,因此我们绝不会在生产环境中遭受任何破坏代价。 (true惯用语规定应精确调用#if DEBUG以便减少完成的开销,但是通过我的机制,可以完全避免生产环境上的开销。)

它可以归结为什么to是永恒的硬错误与软错误参数:IDisposable.Dispose( bool disposing )惯用语是一种软错误方法,它表示尝试使程序员忘记调用GC.SuppressFinalize()而不会导致系统故障(如果可能)。硬错误方法表示程序员必须始终确保将调用IDisposable.Dispose( bool disposing )。在大多数情况下,硬错误方法通常规定的惩罚是断言失败,但是对于这种特殊情况,我们将例外并减少对简单发布错误日志条目的惩罚。

为了使这种机制起作用,我们的应用程序的DEBUG版本必须在退出之前执行完整的垃圾处理,以确保所有析构函数都将被调用,从而捕获我们忘记处理的所有Dispose()对象。

评论


现在,严格来说,当然不可能保证没有程序员会犯忘记忘记调用IDisposable.Dispose()的错误。实际上,不是,尽管我不认为C#能够做到这一点。不要公开资源;取而代之的是提供一个DSL来描述您将要使用的所有内容(基本上是monad),以及一个获取资源,执行操作,释放它并返回结果的函数。诀窍是使用类型系统来确保如果有人走私了对该资源的引用,则该引用不能在对run函数的另一个调用中使用。

–Doval
15年3月19日在17:41

Dispose(布尔处理)(未在IDisposable上定义)的问题在于,它用于处理对象作为字段(或负责)的托管和非托管对象的清理,从而解决了错误如果将所有非托管对象包装在一个托管对象中,而无需担心其他可抛弃对象,则所有Dispose()方法将是其中之一(如果需要,终结器进行相同的清理)或仅具有托管对象进行处理(根本没有终结器),并且不再需要布尔处理。

–琼·汉娜(Jon Hanna)
15年3月22日在12:43

-1错误的建议,因为最终确定实际上是如何工作的。我完全同意您关于dispose(dispose)惯用语是Terribad的观点,但我之所以这样说是因为人们在仅拥有托管资源时经常使用该技术和终结器(例如,托管DbConnection对象,因此它不是粉刺的或无序的) ,并且您永远都不应使用未管理的,粉红色的,COM编组的或不安全的代码实施finalizer。我在上面的回答中详细介绍了终结器的价格是多么昂贵,除非您的课程中没有受管的资源,否则不要使用它们。

–吉米·霍法(Jimmy Hoffa)
15年11月13日在15:49

我几乎想给您+1,尽管仅仅是因为您谴责这么多人将其作为dispose(dispoing)成语中的核心重要内容,但事实是,这是如此普遍,因为人们如此害怕GC的东西诸如此类的无关紧要的事情(应该与GC保持联系)应该使他们仅服用处方药,而无需进行调查。对您进行检查很有利,但您错过了最大的一个整体(它鼓励终结者以比实际情况更多的频率)

–吉米·霍法(Jimmy Hoffa)
15年11月13日在15:52

@JimmyHoffa感谢您的输入。我同意终结器通常只应用于释放非托管资源,但是您是否同意在DEBUG构建中此规则不适用,并且在DEBUG构建中我们应该自由使用终结器捕获错误?这就是我在这里建议的全部内容,因此我不明白您为何对此表示反对。另请参阅developers.stackexchange.com/questions/288715/…,以获取有关Java方面这种方法的详细说明。

– Mike Nakis
2015年11月13日在16:12



#10 楼


您能告诉我在哪种情况下强制垃圾回收实际上是一个好主意吗?我不是在询问C#的特定情况,而是询问所有具有垃圾收集器的编程语言。我知道您不能在所有语言(例如Java)上强制使用GC,但让我们假设您可以。


只是从理论上讲,而无视诸如某些GC实现之类的问题,它们会使它们在运行过程中变慢收集周期,我能想到的最大的方案是强制执行垃圾收集,这是一个关键任务软件,其中逻辑泄漏比悬而未决的指针崩溃更可取,例如,因为在意外时间崩溃可能会导致人员伤亡或此类损失。 >
如果您看一些使用GC语言编写的更独立的独立游戏(如Flash游戏),它们会像疯了似的泄漏而不会崩溃。他们可能会在20分钟的游戏中消耗10倍的内存,因为游戏代码库的某些部分忘记了将引用设置为null或将其从列表中删除,并且帧速率可能开始受到影响,但游戏仍然可以正常工作。类似的使用劣质C或C ++编码编写的游戏可能由于相同类型的资源管理错误而导致访问悬空指针而崩溃,但不会泄漏太多。从可以快速检测和修复的角度来看,崩溃可能是更可取的,但是对于关键任务程序,在完全意外的时间崩溃可能会导致某人死亡。因此,我认为主要情况是不会崩溃或某些其他形式的安全性至关重要的情况,而相比之下,逻辑泄漏则是相对琐碎的事情。

我认为强制使用GC不好的主要情况是,逻辑泄漏实际上比崩溃更可取。例如,对于游戏而言,崩溃并不一定会杀死任何人,并且在内部测试中很容易捕获并修复崩溃,而即使在产品交付后,逻辑泄漏也可能不会被注意到,除非它如此严重以至于在几分钟之内导致游戏无法玩。在某些领域中,测试中发生的易于重现的崩溃有时比没有人立即注意到的泄漏更可取。对于一个寿命很短的程序,就像从命令行执行的那样,它执行一项任务然后关闭。在那种情况下,程序的生命周期太短,以至于无法进行任何形式的逻辑泄漏。逻辑泄漏,即使是大资源,通常在运行软件后几小时或几分钟内就会出现问题,因此只打算执行3秒钟的软件就不可能出现逻辑泄漏问题,这可能会使事情变得很多如果团队仅使用GC,则编写这样的短期程序更为简单。