如何在Java中强制进行垃圾回收?

270

在Java中是否可以强制进行垃圾回收?即使有些棘手,我知道System.gc();Runtime.gc();但它们只是建议进行GC。那么如何强制GC呢?


32
或许为什么需要强制执行GC提供一些背景会有帮助。通常在垃圾收集语言中,显式调用垃圾收集器是一个不好的做法。请注意保持原意,同时使翻译更加易懂,不要添加解释和其他内容。 - Justin Ethier
3
一些JVM可能提供多种垃圾回收方法,每种方法都有其优缺点。通常情况下,只需在启动时提示JVM,就可以避免出现特定的情况。请详细说明这种情景。 - Thorbjørn Ravn Andersen
3
使用命令 "jmap -histo:live <pid>" 时,是否会强制进行垃圾收集?可参考这个链接:https://dev59.com/Q2w15IYBdhLWcg3wv-ba - user2463941
9
这是强制进行垃圾回收的一个应用场景:我有一个带有30GB堆的服务器,其中约有12GB通常被使用(大约5M个对象)。每5分钟,服务器会花费大约一分钟执行一个复杂任务,其中大约使用了35M个额外的对象。几乎每小时都会触发几次完整的垃圾回收,总是在执行复杂任务时发生,并且会导致VM冻结10到15秒钟。我希望能够在不运行复杂任务的时间强制运行完整的垃圾回收;这样,它将只需要处理5M个活跃对象而不是40M个。 - Steve
5
@JustinEthier 有一个非常明显的情况,你可能想要强制进行GC,那就是单元测试涉及java.lang.ref.Reference类型层次结构的任何行为。 - Elias Vasylenko
显示剩余5条评论
25个回答

197
您最好的选择是调用 System.gc() 方法,它只是向垃圾回收器发出一个提示要进行垃圾回收。虽然垃圾收集器是非确定性的,但没有办法强制立即进行垃圾回收。

44
应该有,非确定性等于麻烦。 - Pacerier
12
一个垃圾收集器可能是非确定性的,但仍然可以提供一种立即强制回收的方式。例如,通常情况下,.NET收集器是非确定性的,但调用GC.Collect()可以强制运行它。只是Java选择不暴露这个功能。 - Petr Hudeček
2
根据我的经验,这种方法总是会调用垃圾收集器。它的频率足够高,以至于我绘制的内存使用量与声明的对象数量的图表始终是严格线性的(考虑到填充等因素)。 - Jim Pivarski
我认为通过分配新对象然后不再引用它们,垃圾收集器会自动运行。 - Bionix1441
5
如果您想要确定性行为,您现在可以使用 Epsilon 垃圾收集器。它决定性地从不回收任何东西。 - Holger
显示剩余3条评论

62

jlibs库提供了一个很好的垃圾收集实用程序类。你可以使用WeakReference对象的一个巧妙小技巧来强制进行垃圾收集。

在jlibs中,可以使用RuntimeUtil.gc()方法进行垃圾收集:

   /**
    * This method guarantees that garbage collection is
    * done unlike <code>{@link System#gc()}</code>
    */
   public static void gc() {
     Object obj = new Object();
     WeakReference ref = new WeakReference<Object>(obj);
     obj = null;
     while(ref.get() != null) {
       System.gc();
     }
   }

27
OP请求并且你声称提供“强制垃圾回收”的解决方案。运行GC子系统是一回事,实际回收垃圾又是另一回事。你提供的代码示例明显旨在确保垃圾已被回收。无论如何,由于这是一个非常老的问题,它显然不是关于OP的意愿,而是关于对公众有用性。没有人对单独“强制GC子系统运行”而不回收任何垃圾感兴趣。事实上,人们通常希望得到一个保证:所有的垃圾都已被回收。 - Marko Topolnik
3
基本上,这段代码与 System.gc() 没有什么不同。WeakReference 对象被清除与内存回收无关。我亲自测试过该代码并发现其毫无作用。而极其简单的代码 System.gc(); System.gc(); 的效果要好得多。 - Marko Topolnik
4
你可能没有将它与 System.gc(); System.gc(); 的效率进行比较,但是知道它是否比后者更有效会很有趣。事实上,仅打印调用 System.gc() 次数的信息就足够了。达到 2 的机会非常渺茫。 - Marko Topolnik
1
如果优化器工作得很好,那么代码将被完全消除。优化器可以检测到对象实际上永远不会被使用并删除其分配。因此,即使是System.gc()命令,也无法可靠地发送......(想一想 - 当它到达循环时,没有保证WeakReference仍然指向任何东西。) - Steffen Heil
3
“没有人对‘强制运行GC子系统’而不回收垃圾感兴趣”……其实今天我就对这种行为感兴趣。我很感激有这个答案。我的目的是检查GC日志处理的旋转行为,并获取GC输出的格式。这个小技巧帮助我快速填满了GC日志。” - erik.weathers
显示剩余20条评论

59

如果要强制进行垃圾回收,最好(如果不是唯一的方法)就是编写一个定制的JVM。我认为垃圾收集器是可插拔的,因此您可能只需选择其中一种可用的实现并进行调整。

注意:这不是一个简单的答案。


48
+1 是为了好笑。在进行令人沮丧的调试时,没有什么比有幽默感的人更能让人感到轻松愉快了。当然,除了一个真正可用的答案之外。 - jsh

47

25

是的,强制按照相同顺序和时间调用这两个方法几乎是可能的,它们分别是:

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

即使只有一个要清理的对象,同时使用这两种方法也会强制垃圾收集器使用finalise()方法来释放已分配的内存并执行finalize()方法规定的操作。

然而,使用垃圾收集器是一种可怕的做法,因为它可能会对软件产生比内存更糟糕的负荷,垃圾收集器有自己的线程,无法控制,而且取决于gc使用的算法,可能需要更长的时间,并且被认为非常低效,如果使用gc导致软件变得更糟,那么您应该检查您的软件,因为它肯定存在问题。一个好的解决方案不应该依赖于gc。

注意: 请记住,仅当在最终方法中没有重新分配对象时,此方法才有效,如果发生这种情况,则对象将保持活动状态并可能出现技术上可能的“复活”。


13
不,即使使用这两个命令也不能强制进行垃圾回收。正如其他人已经提到的那样,gc()只是一个提示来运行垃圾回收。runFinalizers()只会对“已被发现已经被丢弃”的对象运行终结器。如果垃圾回收没有实际运行,则可能没有这样的对象存在... - Steffen Heil
1
另外,System.runFinalization()并不保证会运行任何内容;有可能根本什么都不会发生。它只是一个“建议”——来自Javadoc的说明:“调用此方法建议Java虚拟机花费一些精力来运行已被发现丢弃但其finalize方法尚未运行的对象的finalize方法”。 - Kaan

23
根据OutOfMemoryError文档,只有在完整垃圾回收后虚拟机未能重新获取内存时才会抛出该错误。 因此,如果您不断分配内存直到出现错误,则已经强制进行了完整的垃圾回收。
大概您真正想问的问题是“我如何回收我认为应该被垃圾回收的内存?”

22

1
尝试添加一些关于引用的解释、注释或描述。 - veljasije
根据文档,这只是调用System.gc(),而正如已经多次说明的那样,System.gc()并不强制进行垃圾回收。 https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr006.html - Cameron McKenzie

17

手动请求GC(非System.gc())的方法:

  1. 进入JDK中的bin文件夹,例如-C:\Program Files\Java\jdk1.6.0_31\bin
  2. 打开jconsole.exe
  3. 连接到所需的本地进程。
  4. 进入内存选项卡并点击执行GC。

3
抱歉,有误导。请将鼠标悬停在“Perform GC”按钮上。您可以请求JVM执行GC,但不能强制执行。 - Kumaran
@PinkeshSharma,这并不是强制性的。这只是一个请求,可以完全被忽略。 - Pacerier
@Pacerier 在理想的世界中是这样的.. 但是如果你这样做,你会立刻看到内存增加了... - Pinkesh Sharma

14

如何强制Java垃圾回收

以下是几种不同的方法来 强制 Java 垃圾回收。

  1. 点击JConsole的 Perform GC 按钮
  2. 使用JMap的 jmap -histo:live 7544 命令,其中7544是pid
  3. 调用Java诊断控制台的 jcmd 7544 GC.run 命令
  4. 在您的代码中调用 System.gc();
  5. 在您的代码中调用 Runtime.getRuntime().gc();

输入图像描述

这些方法都无法保证有效

这里有个小秘密。这些方法都不能保证起作用。你真的不能强制Java垃圾回收

Java垃圾回收算法是非确定性的,虽然所有这些方法都可以激励JVM进行GC,但实际上你不能强制它。如果JVM负载过重,无法进行全局停顿操作,这些命令要么会报错,要么会运行但GC实际上不会发生。

if (input.equalsIgnoreCase("gc")) {
    System.gc();
    result = "Just some GC.";
}

if (input.equalsIgnoreCase("runtime")) {
    Runtime.getRuntime().gc();
    result = "Just some more GC.";
}

解决该问题

如果您遇到了内存泄漏或对象分配问题,则需要解决它。仅仅在Java Mission Control的 Force Java GC 按钮上闲逛只会推迟问题。使用Java Flight Recorder对应用程序进行剖析,使用VisualVM或JMC查看结果,并解决问题。试图强制Java GC是一场愚人游戏。

输入图片描述


我很惊讶有多少人忽略了 System.gc() 的一个有效用例,或者实际上强制进行垃圾回收:找出实际可达内存的数量。例如,在性能测试期间需要此功能,并确定 -XmX 的基准值 - 显然您不需要在生产中使用它。 - toolforger

12

在未来的版本中,.gc 可能会被淘汰 - 一位 Sun 工程师曾经评论说,世界上可能只有不到二十个人真正知道如何使用 .gc() - 昨晚我做了一些工作,花费了几个小时在一个关键的数据结构上,使用 SecureRandom 生成的数据,在大约 40,000 个对象之后,虚拟机会变慢,好像它已经用完了指针。显然,它被卡在了 16 位指针表上,并展现了经典的 "故障机器" 行为。

我尝试使用 -Xms 等参数,一直进行位操作,直到它可以运行到大约 57,xxx。然后它会在 gc() 后从 57,127 跳到 57,128 - 在像 Easy Money 训练营那样的代码膨胀速度下运行。

你的设计需要进行基本的重新设计,可能需要采用滑动窗口方法。


1
我有类似的情况,许多对象在内存中无法释放。抛出了OutOfMemory异常,我想强制进行垃圾回收以测试是否存在无限对象创建过程或这些对象是系统使用的对象。 - user179172
听起来你正在处理和我一样的问题,请解释:“无限对象创建”...这是一个很好的研究项目,也许你可以在这里的Java区域发布一个问题或者什么的(我在这里还比较新,不知道网站的“有限自动机”是如何工作的)。昨天我尝试了一下,结果编译器抱怨说40,000个基于36进制的BigIntegers被编码为静态final String[]太多代码了。我要冒险猜测一下,整个JVM受到16位指针的限制,我敢打赌我们必须积极地将其置空并从磁盘中读取... - Nicholas Jordan
真的,我不明白你的意思。但是说到“无限对象创建”,我的意思是在我的大型系统中有一些代码可以创建处理和存活在内存中的对象,但我实际上无法理解这段代码,只能形容它! - user179172
6
胡说八道!有一个明显的情况需要使用它:测试使用弱引用的代码,以便我们可以确保在弱引用被清除时行为是正确的。 - Elias Vasylenko
虽然在2009年或之前,Sun的工程师可能告诉过你这个消息,但现在已经是2020年底了,在过去的9年/9个Java版本中,“gc()”并没有被弃用。我认为,弃用/删除将会破坏太多现有的应用程序,因此这不是一个严肃考虑的问题。 - Stephen C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接