ChatGPT解决这个技术问题 Extra ChatGPT

为什么调用 System.gc() 是不好的做法?

answering 提出一个关于如何使用 System.gc() force-free objects in Java(这个人正在清除 1.5GB HashMap)的问题之后,有人告诉我手动调用 System.gc() 是不好的做法,但评论并不完全令人信服。此外,似乎没有人敢赞成,也不敢反对我的回答。

有人告诉我这是不好的做法,但后来我也被告知垃圾收集器运行不会再系统地停止世界,它也可以被 JVM 有效地用作提示,所以我有点不知所措。

我确实理解 JVM 在需要回收内存时通常比您更清楚。我也明白担心几千字节的数据是愚蠢的。我也明白,即使是兆字节的数据也不是几年前的样子。但是,1.5 GB?您知道内存中大约有 1.5 GB 的数据;这不像是在黑暗中拍摄。 System.gc() 系统性地不好,还是在某个时候它变得好起来了?

所以问题实际上是双重的:

为什么调用 System.gc() 是或不是不好的做法?它真的只是在某些实现下对 JVM 的提示,还是它总是一个完整的收集周期?真的有垃圾收集器实现可以在不停止世界的情况下完成它们的工作吗?请阐明人们在对我的回答的评论中所做的各种断言。

门槛在哪里?调用 System.gc() 从来都不是一个好主意,还是有时可以接受?如果是这样,那是什么时候?

我认为调用 System.gc() 的好时机是当你正在做一些已经很长的加载过程时。例如,我正在开发一款游戏,并计划在游戏加载新关卡时(在加载过程结束时)调用 System.gc()。用户已经在等待,额外的性能提升可能是值得的;但我也会在配置屏幕中放置一个选项来禁用此行为。
java-monitor.com/forum/showthread.php?t=188 中解释了另一种情况,其中解释了调用 System.gc() 可能是一种不好的做法
在阅读了一些答案后,我只需要两分钱(应该指出,我对 Java 还很陌生,所以请对我的意见持保留态度),但是......我认为更好的问题可能是“为什么你一开始就有这么大的HashMap?”

C
Cristian Ciupitu

每个人都说要避免使用 System.gc() 的原因是它很好地指示了根本性的代码损坏。任何依赖它来保证正确性的代码肯定会被破坏;任何依赖它来获得性能的东西都很可能被破坏。

您不知道您正在运行哪种垃圾收集器。当然,有些 JVM 不会像您断言的那样“停止世界”,但有些 JVM 并不那么聪明或出于各种原因(也许它们在电话上?)不这样做。你不知道它会做什么。

此外,它不能保证做任何事情。 JVM 可能会完全忽略您的请求。

“你不知道它会做什么”、“你不知道它是否会有所帮助”和“无论如何你都不应该调用它”的组合是为什么人们通常如此有力地说你不应该打电话给它。我认为这是“如果您需要询问是否应该使用它,则不应该”的情况

编辑以解决另一个线程的一些问题:

在阅读了您链接的主题后,我还想指出一些事情。首先,有人建议调用 gc() 可能会将内存返回给系统。这当然不一定是正确的——Java 堆本身的增长独立于 Java 分配。

例如,JVM 将持有内存(数十兆字节)并根据需要增加堆。即使释放 Java 对象,它也不一定会将内存返回给系统;保留分配的内存以用于未来的 Java 分配是完全自由的。

要显示 System.gc() 可能什么都不做,请查看 JDK bug 6668279,特别是有一个 -XX:DisableExplicitGC VM 选项:

默认情况下,对 System.gc() 的调用是启用的(-XX:-DisableExplicitGC)。使用 -XX:+DisableExplicitGC 禁用对 System.gc() 的调用。请注意,JVM 在必要时仍会执行垃圾收集。


您可能能够构建一些奇怪的 Rube Goldberg 式设置,其中运行 GC 的方法会影响代码的正确性。也许它掩盖了一些奇怪的线程交互,或者终结器对程序的运行有重大影响。我不完全确定它是否可能,但它可能是,所以我想我会提到它。
@zneak 例如,您可能已将关键代码放入终结器中(这基本上是损坏的代码)
我想补充一点,在某些极端情况下,System.gc() 很有用,甚至可能是必要的。例如,在 Windows 上的 UI 应用程序中,当您在最小化 Window 之前调用 System.gc() (尤其是当它保持最小化一段时间并且该过程的一部分被交换到磁盘)。
@AndrewJanke 我会说将WeakReference用于您想要保留的对象的代码从一开始就是不正确的,垃圾收集与否。您在使用 std::weak_ptr 的 C++ 中会遇到同样的问题(尽管您可能会注意到 C++ 版本中的问题早于 Java 版本中的问题,因为对象销毁不会像通常的终结那样被推迟)。
@rebeccah 这是一个错误,所以是的,我会称之为“肯定坏了”。 System.gc() 修复它的事实是一种解决方法,而不是良好的编码习惯。
c
codeforester

已经解释过调用 system.gc() 可能 什么都不做,并且任何“需要”垃圾收集器运行的代码都会被破坏。

但是,调用 System.gc() 是不好的做法的实际原因是它效率低下。在最坏的情况下,它效率极低!让我解释。

典型的 GC 算法通过遍历堆中所有非垃圾对象来识别垃圾,并推断任何未访问的对象都必须是垃圾。由此,我们可以对垃圾收集的总工作进行建模,其中一部分与实时数据量成正比,另一部分与垃圾量成正比;即work = (live * W1 + garbage * W2)

现在假设您在单线程应用程序中执行以下操作。

System.gc(); System.gc();

第一次调用将(我们预测)执行 (live * W1 + garbage * W2) 工作,并摆脱未完成的垃圾。

第二个调用将完成 (live* W1 + 0 * W2) 工作并且不回收任何内容。换句话说,我们已经完成了 (live * W1) 的工作,一无所获

我们可以将收集器的效率建模为收集一个单位垃圾所需的工作量;即efficiency = (live * W1 + garbage * W2) / garbage。所以为了让 GC 尽可能高效,我们需要在运行 GC 时最大化 garbage 的值;即等到堆满。 (而且,使堆尽可能大。但这是一个单独的主题。)

如果应用程序不干预(通过调用 System.gc()),GC 将等到堆满再运行,从而高效收集垃圾1。但是如果应用程序强制 GC 运行,很有可能堆不会满,结果是垃圾收集效率低下。而且应用程序强制 GC 的频率越高,GC 的效率就越低。

注意:上面的解释掩盖了一个典型的现代 GC 将堆划分为“空间”,GC 可能会动态扩展堆,应用程序的非垃圾对象的工作集可能会变化等事实。即便如此,同样的基本原则也适用于所有真正的垃圾收集器2。强制 GC 运行是低效的。

1 - 这就是“吞吐量”收集器的工作方式。 CMS 和 G1 等并发收集器使用不同的标准来决定何时启动垃圾收集器。

2 - 我也排除了专门使用引用计数的内存管理器,但当前的 Java 实现没有使用这种方法......这是有充分理由的。


+1 很好的解释。但是请注意,这种推理仅适用于您关心吞吐量的情况。如果您想在特定点优化延迟,强制 GC 可能是有意义的。例如(假设地说)在游戏中您可能希望避免关卡期间的延迟,但您并不关心关卡加载期间的延迟。然后在关卡加载后强制 GC 是有意义的。它确实会降低整体吞吐量,但这不是您要优化的。
@sleske - 你说的是真的。但是,在关卡之间运行 GC 是一种创可贴的解决方案……如果关卡花费的时间足够长以至于您无论如何都需要在关卡期间运行 GC,那么它并不能解决延迟问题。更好的方法是使用并发(低暂停)垃圾收集器......如果平台支持它。
不完全确定“需要垃圾收集器运行”是什么意思。应用程序需要避免的条件是;永远无法满足的分配失败和高 GC 开销。随机调用 System.gc() 通常会导致高 GC 开销。
@Kirk - “随机调用 System.gc() 通常会导致高 GC 开销。”。我知道。任何阅读并理解我的答案的人也会如此。
@Kirk - “不完全确定你的意思......” - 我的意思是程序的“正确”行为取决于在特定时间运行的 GC;例如,为了执行终结器,或破坏 WeakReferences 或 SoftReferences。这样做是一个非常糟糕的主意……但这就是我要说的。另见史蒂文施兰斯克的回答。
K
KitsuneYMG

很多人似乎在告诉你不要这样做。我不同意。如果在加载关卡等大型加载过程之后,您认为:

您有很多无法访问且可能尚未被 gc'ed 的对象。并且您认为用户此时可以忍受小幅减速

调用 System.gc() 没有害处。我把它看成 c/c++ inline 关键字。这只是对 gc 的一个提示,您,开发人员,已经决定时间/性能并不像通常那样重要,并且其中一些可以用于回收内存。

不依赖它做任何事情的建议是正确的。不要依赖它的工作,但暗示现在是一个可以接受的收集时间是完全可以的。我宁愿在代码中无关紧要的地方(加载屏幕)浪费时间,也不愿在用户积极与程序交互时(比如在游戏关卡中)。

有一次我会force收集:当试图找出某个特定对象泄漏时(本机代码或大型、复杂的回调交互。哦,还有任何 UI 组件,只要瞥一眼 Matlab。)这绝不应该在生产代码中使用。


在分析内存泄漏时为 GC +1。请注意,有关堆使用情况的信息(Runtime.freeMemory() 等)仅在强制 GC 后才真正有意义,否则它将取决于系统上次运行 GC 的时间。
调用 System.gc() 没有害处它可能需要 stop the world 方法,如果发生这种情况,这是真正的害处
显式调用垃圾收集器是有害的。在错误的时间调用 GC 会浪费 CPU 周期。您(程序员)没有足够的信息来确定何时是正确的时间……但 JVM 有。
Jetty 在启动后对 System.gc() 进行了 2 次调用,但我很少看到它有什么不同。
码头开发人员有一个错误。它会有所作为……即使这种差异难以量化。
J
Joachim Sauer

人们一直在很好地解释为什么不使用它,所以我会告诉你几个你应该使用它的情况:

(以下评论适用于在 Linux 上使用 CMS 收集器运行的 Hotspot,我有信心说 System.gc() 实际上总是调用完整的垃圾收集)。

在启动您的应用程序的初始工作之后,您的内存使用情况可能很糟糕。你有一半的老一代可能充满了垃圾,这意味着你离你的第一个 CMS 更近了。在重要的应用程序中,调用 System.gc() 将堆“重置”为实时数据的起始状态并不是一个坏主意。与 #1 相同,如果您密切监视堆使用情况,您希望准确了解基线内存使用情况。如果您的应用程序正常运行时间的前 2 分钟都是初始化,那么您的数据将会被弄乱,除非您强制(咳咳……“建议”)预先执行完整的 gc。您可能有一个应用程序,它被设计为在运行时永远不会向终身代提升任何东西。但也许您需要预先初始化一些数据,这些数据不是那么大,以至于自动转移到终身代。除非您在所有内容都设置好后调用 System.gc(),否则您的数据可能会保留在新一代中,直到需要升级为止。突然之间,您的超级低延迟、低 GC 应用程序会因在正常操作期间提升这些对象而遭受巨大的(当然是相对而言)延迟损失。有时在生产应用程序中调用 System.gc 来验证是否存在内存泄漏很有用。如果您知道时间 X 的实时数据集与时间 Y 的实时数据集应以一定比例存在,那么在时间 X 和时间 Y 调用 System.gc() 并比较内存使用情况可能会很有用.


对于大多数分代垃圾收集器,新生代中的对象必须在一定数量(通常可配置)的垃圾收集中存活下来,因此调用 System.gc() 一次来强制提升对象不会给您带来任何好处。而且您肯定不想连续调用 System.gc() 八次并祈祷现在提升已经完成,并且以后提升所节省的成本证明了多次完整 GC 的成本是合理的。根据 GC 算法,提升大量对象甚至可能不承担实际成本,因为它只会将内存重新分配给老年代或并发复制......
D
Dmitry

这是一个非常令人烦恼的问题,尽管 Java 是一种非常有用的语言,但我觉得它促成了许多反对 Java 的人。

您不能信任“System.gc”做任何事情的事实令人难以置信,并且可以轻松地唤起该语言的“恐惧,不确定性,怀疑”感觉。

在许多情况下,最好在重要事件发生之前处理您故意造成的内存峰值,这会导致用户认为您的程序设计不良/反应迟钝。

拥有控制垃圾收集的能力将是一个很好的教育工具,进而提高人们对垃圾收集如何工作以及如何使程序利用它的默认行为和受控行为的理解。

让我回顾一下这个线程的论点。

这是低效的:

通常,该程序可能没有做任何事情,并且您知道它没有做任何事情,因为它的设计方式。例如,它可能正在使用一个大的等待消息框进行某种长时间的等待,最后它还可以添加一个收集垃圾的调用,因为运行它的时间将只占用运行时间的一小部分。漫长的等待,但会避免 gc 在更重要的操作中发挥作用。

这始终是一种不好的做法,并表明代码损坏。

我不同意,你有什么垃圾收集器并不重要。它的工作是跟踪和清理垃圾。

通过在使用不那么重要的时候调用 gc,当你的生活依赖于正在运行的特定代码但它决定收集垃圾时,你可以降低它运行的几率。

当然,它可能不会按照您想要或期望的方式运行,但是当您确实想调用它时,您知道什么都没有发生,并且用户愿意容忍缓慢/停机时间。如果 System.gc 有效,那就太好了!如果没有,至少你试过了。除非垃圾收集器具有固有的副作用,如果手动调用垃圾收集器的行为方式会产生可怕的意外,并且这本身会引起不信任,否则根本没有不利的一面。

这不是一个常见的用例:

这是一个无法可靠实现的用例,但如果系统是以这种方式设计的,则可能是这样。这就像制作一个红绿灯并使其部分/所有红绿灯的按钮不做任何事情,这让你质疑为什么按钮在那里开始,javascript没有垃圾收集功能所以我们没有不要为此仔细检查它。

规范说 System.gc() 是 GC 应该运行的提示,VM 可以随意忽略它。

什么是“提示”?什么是“忽略”?计算机不能简单地接受提示或忽略某事,它采用严格的行为路径,这些路径可能是动态的,由系统的意图引导。一个正确的答案将包括垃圾收集器在实现级别实际上在做什么,这会导致它在您请求它时不执行收集。该功能只是一个nop吗?我必须满足某种条件吗?这些条件是什么?

就目前而言,Java 的 GC 通常看起来像是一个您不信任的怪物。你不知道它什么时候来或去,你不知道它会做什么,它会怎么做。我可以想象一些专家对他们的垃圾收集如何在每条指令的基础上工作有更好的了解,但绝大多数人只是希望它“正常工作”,而不得不相信一个看似不透明的算法来为你工作是令人沮丧的。

在阅读某事或被教导某事与实际看到它的实现、系统之间的差异以及无需查看源代码即可使用它之间存在很大差距。这会产生自信和掌握/理解/控制的感觉。

总而言之,答案存在一个固有的问题“这个功能可能不会做任何事情,我不会详细说明如何判断它什么时候做某事,什么时候不做,以及为什么它不会或会做某事,经常暗示尝试这样做完全违反哲学,即使其背后的意图是合理的”。

Java GC 的行为方式可能是可以的,也可能不是,但要理解它,很难真正遵循哪个方向去全面了解您可以信任 GC 做什么和不要这样做,所以简单地不信任语言太容易了,因为语言的目的是在哲学范围内控制行为(程序员,尤其是新手很容易因某些系统/语言行为而陷入生存危机)你有能力容忍(如果你不能,你就不会使用这种语言,直到你不得不这样做),而更多你无法控制的事情,因为你无法控制它们,本质上是有害的。


I
Ian Ringrose

有时(不经常!)您确实比运行时更了解过去、当前和未来的内存使用情况。这种情况不会经常发生,我会声称在提供普通页面时永远不会在 Web 应用程序中。

许多年前,我在一个报告生成器上工作,那个

有一个线程

从队列中读取“报告请求”

从数据库中加载报表所需的数据

生成报告并通过电子邮件发送出去。

永远重复,在没有未完成的请求时休眠。

它没有在报告之间重复使用任何数据,也没有进行任何兑现。

首先,由于它不是实时的,并且用户希望等待报告,因此 GC 运行时的延迟不是问题,但我们需要以比要求更快的速度生成报告。

看上面的流程大纲,就很清楚了。

我们知道,在通过电子邮件发送报告后,活动对象将非常少,因为下一个请求尚未开始处理。

众所周知,运行一次垃圾回收周期的成本取决于存活对象的数量,垃圾量对 GC 运行的成本影响不大。

当队列为空时,没有什么比运行 GC 更好的办法了。

因此很明显,当请求队列为空时执行 GC 运行是非常值得的;这没有缺点。

在通过电子邮件发送每个报告后运行 GC 可能是值得的,因为我们知道这是运行 GC 的好时机。但是,如果计算机有足够的内存,则通过延迟 GC 运行可以获得更好的结果。

此行为是在每个安装基础上配置的,对于某些客户而言,在每次报告后启用强制 GC 大大加快了报告的生成速度。 (我预计这是由于他们的服务器上的内存不足,并且它运行了许多其他进程,因此很好的时间强制 GC 减少了分页。)

每次工作队列为空时,我们从未检测到没有从强制 GC 运行中受益的安装。

但是,请明确一点,上述情况并不常见。

这些天来,我更倾向于在单独的进程中运行每个报告,让操作系统清理内存而不是垃圾收集器,并让自定义队列管理器服务在大型服务器上使用多个工作进程。


T
Thomas Pornin

GC 效率依赖于许多启发式方法。例如,一个常见的启发式方法是对对象的写访问通常发生在不久前创建的对象上。另一个是许多对象的寿命很短(有些对象会使用很长时间,但许多对象会在创建后几微秒内被丢弃)。

调用 System.gc() 就像踢 GC。它的意思是:“所有那些精心调整的参数,那些聪明的组织,你为分配和管理对象所做的所有努力,以使事情顺利进行,好吧,放下所有的一切,从头开始”。它可能提高性能,但大多数时候它只是降低性能。

要可靠地使用 System.gc()(*),您需要了解 GC 的所有细节是如何运行的。如果您使用来自其他供应商的 JVM,或者使用来自同一供应商的下一个版本,或者使用相同的 JVM 但命令行选项略有不同,这些细节往往会发生很大变化。因此,除非您想解决控制所有这些参数的特定问题,否则这很少是一个好主意。因此出现了“不良做法”的概念:这不是被禁止的,这种方法是存在的,但它很少有回报。

(*) 我在这里谈论的是效率。 System.gc() 永远不会破坏正确的 Java 程序。它不会产生 JVM 否则无法获得的额外内存:在抛出 OutOfMemoryError 之前,JVM 会完成 System.gc() 的工作,即使是最后的手段。


+1 提到 System.gc() 不能防止 OutOfMemoryError。有些人相信这一点。
实际上,由于软引用的处理,它可能会阻止 OutOfMemoryError。在我知道的实现中不会收集上次 GC 运行后创建的 SoftReferences。但这是随时可能更改的实现细节和一种错误,您不应该依赖任何东西。
R
Ryan Fernandes

也许我写了蹩脚的代码,但我开始意识到单击 eclipse 和 netbeans IDE 上的垃圾桶图标是一种“好习惯”。


可能是这样。但是,如果 Eclipse 或 NetBeans 被编程为定期调用 System.gc(),您可能会发现这种行为很烦人。
t
tom

是的,调用 System.gc() 并不能保证它会运行,它是对 JVM 的请求,可能会被忽略。从文档:

调用 gc 方法表明 Java 虚拟机花费精力来回收未使用的对象

调用它几乎总是一个坏主意,因为自动内存管理通常比你更清楚何时使用 gc。当它的内部可用内存池不足时,或者如果操作系统请求返还一些内存,它就会这样做。

如果您知道它有帮助,调用 System.gc() 可能是可以接受的。我的意思是你已经在部署平台上彻底测试和测量了这两种场景的行为,你可以证明它有帮助。请注意,虽然 gc 不容易预测 - 它可能有助于一次运行并伤害另一次。


还有来自 Javadoc:_当控制从方法调用返回时,虚拟机已尽最大努力回收所有丢弃的对象,我认为这是您发布的内容的一种更为必要的形式。 搞砸了,有一个关于它具有误导性的错误报告。哪位更清楚,提示 JVM 的危害是什么?
危害是在错误的时间进行收集可能会大大减慢速度。您给出的提示可能很糟糕。至于“尽力而为”的评论,请尝试并在 JConsole 之类的工具中查看。有时单击“Perform GC”按钮无济于事
很抱歉不同意,但在 OpenJDK 中调用 System.gc() 以及任何基于它的东西(例如 HP)总是会导致垃圾回收周期。事实上,IBM 的 J9 实现似乎也是如此
@Kirk - 不正确:谷歌,并阅读 -XX:-DisableExplicitGC。
K
Kirk

首先,规格和现实之间存在差异。规范说 System.gc() 是 GC 应该运行的提示,VM 可以随意忽略它。现实情况是,VM 永远不会忽略对 System.gc() 的调用。

调用 GC 会带来不小的开销,如果您在某个随机时间点执行此操作,您的努力可能不会得到任何回报。另一方面,自然触发的收款很有可能收回通话成本。如果您有指示应该运行 GC 的信息,那么您可以调用 System.gc() 并且您应该会看到好处。但是,根据我的经验,这仅在少数极端情况下发生,因为您不太可能有足够的信息来了解是否以及何时应该调用 System.gc()。

此处列出的一个示例是在您的 IDE 中使用垃圾桶。如果你要去开会,为什么不去开会。开销不会影响您,并且当您回来时可能会清理堆。在生产系统中执行此操作,频繁调用 collect 会使其停止运行!即使是偶尔的调用,例如 RMI 发出的调用,也会对性能造成破坏。


“现实情况是,VM 永远不会忽略对 System.gc() 的调用。” - 不正确。阅读 -XX:-DisableExplicitGC。
M
Mike Nakis

我将要写的一些内容只是对其他答案中已经写过的内容的总结,还有一些是新的。

问题“为什么调用 System.gc() 是不好的做法?”不计算。它假定这是不好的做法,但事实并非如此。这在很大程度上取决于您要完成的工作。

绝大多数程序员不需要 System.gc(),并且在绝大多数用例中它永远不会对他们做任何有用的事情。因此,对于大多数人来说,调用它是不好的做法,因为它不会做他们认为会做的任何事情,它只会增加开销。

但是,在少数情况下调用 System.gc() 实际上是有益的:

当您绝对确定您现在有一些 CPU 时间可用,并且您希望提高稍后运行的代码的吞吐量时。例如,Web 服务器发现此时没有待处理的 Web 请求,现在可以启动垃圾收集,以减少在以后处理一连串 Web 请求时需要进行垃圾收集的机会。 (当然,如果 Web 请求在收集期间到达,这可能会造成伤害,但 Web 服务器可能对此很聪明,如果请求进入,则放弃收集。)桌面 GUI 是另一个示例:在空闲事件(或更广泛地说,在一段时间不活动,)你可以给 JVM 一个提示,如果它有任何垃圾收集要做,现在总比以后好。当您想检测内存泄漏时。这通常与仅调试模式的终结器或 Java 9 及更高版本的 java.lang.ref.Cleaner 类结合使用。这个想法是,通过现在强制垃圾收集,从而发现现在的内存泄漏而不是将来的某个随机时间点,您可以在内存泄漏发生后尽快检测到,因此处于更好的位置准确判断哪段代码泄漏了内存以及原因。 (顺便说一句,这也是终结器或清洁器的合法用例之一,或者可能是唯一的合法用例。使用终结器来回收非托管资源的做法是有缺陷的,尽管它非常普遍甚至官方推荐,因为它不是确定性。有关此主题的更多信息,请阅读:https://blog.michael.gr/2021/01/object-lifetime-awareness.html)当您测量代码的性能时,(基准测试)以减少/最小化在基准测试期间发生垃圾收集的机会,或者为了保证在基准测试期间由于垃圾收集而遭受的任何开销都是由于基准测试代码生成的垃圾,而不是无关代码。一个好的基准总是从尽可能彻底的垃圾收集开始。当你在测量代码的内存消耗时,为了确定一段代码产生了多少垃圾。想法是执行一次完整的垃圾回收,以从干净的状态开始,运行被测量的代码,获取堆大小,然后再进行一次完整的垃圾回收,再次获取堆大小,并取差值。 (顺便说一句,在运行被测代码时暂时抑制垃圾收集的能力在这里很有用,可惜 JVM 不支持。这很遗憾。)

请注意,在上述用例中,只有一个是在生产场景中;其余的都在测试/诊断场景中。

这意味着 System.gc() 在某些情况下可能非常有用,这反过来意味着它“只是一个提示”是有问题的。

(只要 JVM 不提供一些确定性和有保证的控制垃圾收集的方法,JVM 在这方面就被破坏了。)

以下是如何将 System.gc() 变成少一点提示的方法:

private static void runGarbageCollection()
{
    for( WeakReference<Object> ref = new WeakReference<>( new Object() ); ; )
    {
        System.gc(); //optional
        Runtime.getRuntime().runFinalization(); //optional
        if( ref.get() == null )
            break;
        Thread.yield();
    }
}

这仍然不能保证你会得到一个完整的 GC,但它更接近了。具体来说,即使使用了 -XX:DisableExplicitGC VM 选项,它也会为您提供一些垃圾收集。 (因此,它真正使用 System.gc() 作为提示;它不依赖它。)


s
sleske

根据我的经验,使用 System.gc() 实际上是一种特定于平台的优化形式(其中“平台”是硬件架构、操作系统、JVM 版本和可能的更多运行时参数(例如可用 RAM)的组合),因为它的行为,虽然在特定平台上大致可预测,但可以(并且将会)在平台之间有很大差异。

是的,在某些情况下 System.gc() 会提高(感知)性能。例如,如果延迟在您的应用程序的某些部分是可以容忍的,但在其他部分是不能容忍的(上面引用的游戏示例,您希望 GC 在关卡开始时发生,而不是在关卡期间发生)。

然而,它是否会帮助或伤害(或什么都不做)在很大程度上取决于平台(如上所述)。

所以我认为它作为最后的平台特定优化是有效的(即如果其他性能优化还不够的话)。但是你永远不应该仅仅因为你相信它可能会有所帮助(没有特定的基准)就调用它,因为它很可能不会。


T
Tanmay Delhikar

由于对象是使用 new 运算符动态分配的,您可能想知道如何销毁这些对象以及如何释放它们的内存以供以后重新分配。在某些语言中,例如 C++,动态分配的对象必须通过使用删除操作符手动释放。 Java 采用了不同的方法。它会自动为您处理释放。实现这一点的技术称为垃圾收集。它的工作原理是这样的:当不存在对某个对象的引用时,假定不再需要该对象,并且可以回收该对象占用的内存。没有像在 C++ 中那样明确需要销毁对象。垃圾收集仅在程序执行期间偶尔发生(如果有的话)。它不会仅仅因为存在一个或多个不再使用的对象而发生。此外,不同的 Java 运行时实现将采用不同的垃圾收集方法,但在大多数情况下,您不必在编写程序时考虑它。