有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java为什么调用系统是不好的做法。gc()?

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

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

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

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

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

共 (6) 个答案

  1. # 1 楼答案

    这是一个非常令人烦恼的问题,我觉得这有助于许多人反对Java,尽管Java是一种多么有用的语言

    你不能信任“System.gc”去做任何事情,这一事实让人难以置信,而且很容易让语言产生“恐惧、不确定、怀疑”的感觉

    在许多情况下,在重要事件发生之前处理您故意造成的内存峰值是很好的,这会导致用户认为您的程序设计不好/没有响应

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

    让我回顾一下这个线程的参数

    1. 这是低效的:

    通常情况下,程序可能什么都没做,而您知道,由于它的设计方式,它什么都没做。例如,它可能会用一个大的等待消息框做一些长时间的等待,最后它还可以添加一个调用来收集垃圾,因为运行它的时间将花费很长的等待时间的一小部分,但是将避免GC在更重要的操作的中间起作用。p>

    1. 这通常是一种不好的做法,表示代码已损坏

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

    通过在使用不太关键的时候调用gc,当您的生活依赖于正在运行的特定代码,但它决定收集垃圾时,您可以降低gc运行的几率

    当然,它可能不会按照您想要或期望的方式运行,但当您确实想要调用它时,您知道什么都没有发生,并且用户愿意容忍缓慢/停机。如果系统发生故障。gc工作,太棒了!如果没有,至少你试过了。除非垃圾收集器具有固有的副作用,这些副作用会对手动调用垃圾收集器时的行为产生可怕的意外影响,否则根本没有负面影响,而这本身会导致不信任

    1. 这不是常见的用例:

    这是一个不能可靠地实现的用例,但是如果系统是这样设计的,它可能会实现。这就像制作一个红绿灯,使它的一些/所有红绿灯按钮不起任何作用,它让你质疑为什么按钮会出现在那里,javascript没有垃圾收集功能,所以我们没有仔细检查它

    1. 说明书上说是这个系统。gc()是一个提示,提示gc应该运行,VM可以随意忽略它

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

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

    阅读一些东西或被教授一些东西,与实际看到它的实现、不同系统之间的差异,以及能够在不看源代码的情况下使用它之间存在很大的差距。这创造了自信和掌握/理解/控制的感觉

    总而言之,有一个内在的原因回答“此功能可能不会做任何事情,我不会详细说明如何判断它何时做某事、何时不做以及为什么不做或将要做,这通常意味着尝试这样做完全违背了哲学,即使其背后的意图是合理的”

    Java GC的行为方式可以是这样,也可以不是这样,但要理解它,很难真正按照哪个方向来全面了解您可以信任GC做什么和不做什么,因此很容易不信任该语言,因为语言的目的是在哲学的范围内控制行为(程序员,尤其是新手很容易因为某些系统/语言行为而陷入生存危机),所以你有能力容忍(如果你不能容忍,那么你只能在不得不容忍之前不使用语言),还有更多你无法控制的事情,因为你不知道为什么你不能控制它们,这本身就是有害的

  2. # 2 楼答案

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

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

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

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


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

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

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

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

    By default calls to System.gc() are enabled (-XX:-DisableExplicitGC). Use -XX:+DisableExplicitGC to disable calls to System.gc(). Note that the JVM still performs garbage collection when necessary.

  3. # 3 楼答案

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

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

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

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

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

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

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

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

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

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


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

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

  4. # 4 楼答案

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

    1. 你有很多无法访问的对象,可能还没有gc'ed。而且
    2. 你认为用户在这一点上可以忍受一点小小的减速吗

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

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

    有一次我将force收集:当我试图找出某个特定对象是否泄漏时(无论是本机代码还是大型复杂回调交互。哦,还有任何一个UI组件,只要看一眼Matlab就知道了。)生产代码中不应使用此选项

  5. # 5 楼答案

    人们已经很好地解释了为什么不使用它,所以我将告诉您一些应该使用它的情况:

    {cdcms}在热点上运行垃圾收集时,我总是使用以下注释:{cdcms}将垃圾收集应用于热点

    1. 在启动应用程序的初始工作之后,内存使用情况可能会非常糟糕。你的终身一代中有一半人可能都是垃圾,这意味着你离你的第一个CMS就那么近了。在重要的应用程序中,调用系统并不是一个坏主意。gc()将堆“重置”为活动数据的开始状态

    2. 按照与#1相同的思路,如果密切监视堆的使用情况,则希望准确地读取基线内存使用情况。如果应用程序正常运行的前2分钟都是初始化,那么除非您提前强制(嗯…“建议”)完整的gc,否则您的数据将被弄乱

    3. 您可能有一个应用程序,该应用程序的设计目的是在运行时永远不会将任何内容升级到终身一代。但是,也许您需要预先初始化一些数据,这些数据不会太大,以便自动移动到终身一代。除非你呼叫系统。gc()在一切都设置好之后,您的数据可能会保存在新的一代中,直到它得到升级。在正常操作期间升级这些对象时,超级复制程序低延迟、低GC应用程序会突然受到巨大(当然,相对而言)延迟惩罚

    4. 有一个系统有时是有用的。生产应用程序中可用的gc调用,用于验证内存泄漏的存在。如果您知道时间X的实时数据集应该与时间Y的实时数据集以一定的比例存在,那么调用系统可能会很有用。gc()计算时间X和时间Y,并比较内存使用情况

  6. # 6 楼答案

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

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

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

    (*)我这里讲的是效率System.gc()永远不会中断正确的Java程序。它不会产生JVM无法获得的额外内存:在抛出OutOfMemoryError之前,JVM执行System.gc()的工作,即使是作为最后手段