【问题标题】:Inducing Java to do lengthy garbage collection诱导 Java 进行冗长的垃圾回收
【发布时间】:2018-07-07 19:09:36
【问题描述】:

我想向我的学生证明,在实时系统中使用 Java 可能会出现问题,因为 Java 可能会进行意外的垃圾收集。我如何编写一个 Java 程序:

  1. 可能会导致 Java 在意外时间停止并执行垃圾收集(没有 System.gc());
  2. 垃圾收集需要很长的时间(例如几秒钟)?

如果这很重要,我在 Ubuntu 16.04 上使用 Open JDK 8 和 Oracle JDK 8。

如果不能同时做到这两个,那么我至少会满意第 2 项,即当我执行 System.gc() 时垃圾收集需要很长时间的程序。

注意:我不是在寻找垃圾收集过程的图形表示 - 只是为了表明它需要很长时间。

【问题讨论】:

  • 那么到目前为止你尝试过什么?
  • 这……很难。如果不知道 Open JDK 8 在内部是如何工作的,那将是不可能的。另外...对于(2),您可以让 finalize 函数等待几秒钟(???)
  • 如果您愿意,您可以随时通过您最喜欢的分析工具触发 GC。
  • Oracle 有一个垃圾收集指南,其中包含一个动手演示应用程序:oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/… - 我不能说我已经尝试过了,但它可能值得一看。

标签: java garbage-collection demo


【解决方案1】:

小型通用 java 程序无法在几秒钟内产生 GC,但是,您可以运行一个程序,创建一些堆大小非常小的对象,并在执行程序时使用下面的开关,这些开关将打印 GC 日志的详细信息,易于解释。

-XX:+UseSerialGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps

即。 java -XX:+UseSerialGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps abc.java

【讨论】:

    【解决方案2】:

    您可能想查看“Java 的实时规范”here。 您可能还想通读 this,这是对 Java 实时编程的介绍。

    【讨论】:

      【解决方案3】:

      你可以从一个比较大的堆空间java -Xmx512m ...开始,以获得一个足够杂乱的堆。

      然后创建许多对象。具有循环的最佳图(具有长路径的循环引用)。让他们中的许多人变得过时。最好的多线程。

      显示一些垃圾回收会停止的动画。最好在图表和 gc 中显示步骤时间; Runtime.freeMemory().

      使用 jvm 监视器。现在是介绍内存和 CPU 分析的好时机;在 NetBeans 或 eclipse 中。

      【讨论】:

      • 垃圾收集器不会遍历死对象。循环也不会显着影响性能。如果垃圾收集器遇到指向已经遇到的对象的引用,您认为会发生什么?
      • @Holger 我想要大的、交织的、循环的列表(图表)。为了确定这些结构是否死亡,(gc) 比 O(N) 具有更高的阶数,N 是新的递归不可达对象。您认为这种方法可行吗?还是我错了?如果你能详细说明一下就好了。
      • 不存在“递归无法访问的对象”之类的东西。垃圾收集器将从垃圾收集根(活动线程的局部变量和永久类的static 变量)开始遍历可达 对象。它在标记过程中没有遇到的所有对象本身都是垃圾,无论这些死对象如何相互引用。在复制收集器的情况下,所有幸存对象都被转移到新的内存空间,之后,旧空间被认为是空闲的,无需任何努力。它甚至不知道有多少物体刚刚死去。
      • @Holger,我明白了;当然 gc 不是从一些无法访问的对象开始(这不可能意味着处理每个分配),而是从所有可访问的对象开始。如果root->r1000->r999->....->r2->r1 则标记 r1 可能需要一些时间,具体取决于对象的分支。标记和扫描只需要 O(N_alive)。我想知道如何重载 gc,我怀疑对象终结是否会有所帮助或被认为是真实的。或者 CPU 密集型工作会损害 gc。谢谢,你说服了我。
      • 使用非平凡的终结器创建大量对象会产生很大的开销(即使在对象创建时),但与典型的 Java 程序的行为相去甚远。大型线性活动对象链可能会增加标记时间,但是当创建大量这些对象时,最旧的最终将被提升到下一代,对于老一代,JVM 跟踪修改,只处理那些已更改的对象自上次垃圾收集以来。最后但同样重要的是,G1 收集器有一个可配置的最大暂停时间目标……
      【解决方案4】:

      原因 System.gc() 需要很长时间,因为它使垃圾收集器进行完全收集,基本上是查看堆中的所有对象。

      如果你创建了很多对象并让它们在年轻时死去,大多数情况下都会发生年轻的收集。年轻(次要)收集比主要收集快得多,因为它只在年轻代空间中查找对象。

      为了确保长时间的 gc 暂停,我建议将对象引用保持在堆几乎已满的程度,并释放一些较旧的对象引用并尝试分配更多对象,这样可以确保可能会发生主要收集,这将导致更长的延迟。

      您可以使用jdk工具飞行记录器(jmc)查看延迟。

      【讨论】:

        【解决方案5】:

        您是否考虑过,如果您无法重现,您告诉学生的内容通常不正确?如果您对实时的玩具定义是“暂停时间少于 1 秒”,那么许多 JVM 应用程序都可以被视为实时。

        这还没有求助于无休止的收集器。

        当然,也可以简单地构建 GC 花费超过 1 秒的情况,例如通过分配足够大的堆以便系统开始交换。但在这种情况下,非 GC 的语言也会出现延迟峰值——也许没有那么糟糕——因此它不能很好地证明 GC 问题。

        System.gc() 也是一个糟糕的演示,因为它当前调用 单线程 集合,而默认收集器的正常操作使用多个内核。 Fixes are underway

        要构建一个有点现实的场景,让现代收藏家会经历 >1 秒的停顿,您需要

        • 大堆 - 多个内核通常可以很快地咀嚼小堆
        • 大型活动集大小 - 死对象很便宜
        • 大量小对象 - 大型基元数组通常很快就能收集到
        • 对象之间的大量引用 - 空字段不需要更新。随机对象图也让 G1GC 的生活变得更加困难

        由于您使用的是 OpenJDK,因此您只是在测试针对桌面和服务器级工作负载的 JVM 收集器。还有其他 JVM 和 3rd-party garbage collector implementations 以实时目标为目标,因此人们可以简单地将您的演示视为将速降自行车称为可怕的自行车,因为您无法用它赢得赛车场比赛。

        【讨论】:

        • 这也是我的第一个想法。如果一个说法很难证明,那么值得重新考虑这个说法。
        猜你喜欢
        • 2014-03-09
        • 1970-01-01
        • 1970-01-01
        • 2011-06-25
        • 2016-01-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多