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

Java 为什么调用System.gc()是一种不好的做法?,java,garbage-collection,Java,Garbage Collection,在一个关于如何使用System.gc()的问题(那家伙正在清除一个1.5GB的HashMap)后,我被告知手动调用System.gc()是一种不好的做法,但这些评论并不完全令人信服。此外,似乎没有人敢投我的赞成票,也没有人敢投我的反对票 那里有人告诉我这是一种不好的做法,但后来我还被告知垃圾收集器的运行不再系统地停止世界,而且它也可以被JVM有效地用作提示,所以我有点不知所措 我知道JVM在需要回收内存时通常比您更清楚。我也明白担心几千字节的数据是愚蠢的。我也明白,即使是兆字节的数据也不是几年前

在一个关于如何使用
System.gc()
的问题(那家伙正在清除一个1.5GB的HashMap)后,我被告知手动调用
System.gc()
是一种不好的做法,但这些评论并不完全令人信服。此外,似乎没有人敢投我的赞成票,也没有人敢投我的反对票

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

我知道JVM在需要回收内存时通常比您更清楚。我也明白担心几千字节的数据是愚蠢的。我也明白,即使是兆字节的数据也不是几年前的情况。但仍然是1.5千兆字节?你知道内存中大约有1.5 GB的数据;这不像是在黑暗中开枪。
System.gc()
是系统性的坏,还是在某个时候它变得正常了

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

  • 为什么调用
    System.gc()
    是一种不好的做法?它真的仅仅是在某些实现下对JVM的一个提示,还是总是一个完整的收集周期?是否真的有垃圾收集器实现可以在不影响世界的情况下完成其工作?请解释一下人们在对我的报告的评论中所作的各种断言
  • 门槛在哪里?调用
    System.gc()
    从来都不是一个好主意,还是有时可以接受?如果是,那是什么时间

每个人都说要避免使用
System.gc()
的原因是,它是一个非常好的指示器,可以指示根本性损坏的代码。任何依赖于它的代码的正确性肯定是被破坏的;任何依赖它来实现性能的系统都很可能会崩溃

你不知道你运行的是哪种垃圾收集器。当然,有些JVM并没有像你所说的那样“阻止世界”,但有些JVM并没有那么聪明,或者出于各种原因(也许他们在打电话?)没有这么做。你不知道它会做什么

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

“你不知道它会起什么作用”、“你甚至不知道它是否有用”和“你无论如何都不需要叫它”的组合就是为什么人们如此强烈地说,一般来说你不应该叫它。我认为这是一个“如果你需要问你是否应该使用这个,你不应该”


编辑以解决其他线程的一些问题:

在阅读了你链接的帖子之后,我还想指出一些事情。 首先,有人建议调用
gc()
可能会将内存返回给系统。这当然不一定是真的——Java堆本身的增长与Java分配无关

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

要显示
System.gc()
可能不执行任何操作,请查看:

特别是有一个VM选项。

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

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

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


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

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

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

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

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

System.gc(); System.gc();
我们预测,第一个调用将执行
(live*W1+garbage*W2)
工作,并清除未处理的垃圾

第二个调用将执行
(live*W1+0*W2)
工作,但不回收任何内容。换句话说,我们做了
(live*W1)
的工作,却一无所获

我们可以将收集器的效率建模为收集一单位垃圾所需的工作量;i、 e.
效率=(活动*W1+垃圾*W2)/垃圾
。因此,为了使GC尽可能高效,我们需要在运行GC时最大化
垃圾
的价值;i、 e.等待堆满。(同时,尽可能地增大堆。但这是一个单独的主题。)

如果应用程序不进行干扰(通过调用
System.gc()
),则gc将等待堆满后再运行,从而有效地收集垃圾1。但如果
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();
    }
}