在阅读了一些关于jvm的文章之后,我想知道是否有一些特定的原因使人们只看到使用基于跟踪的垃圾收集器。我知道jvm规范将这些设计选择留给jvm的实现者。引用计数垃圾收集器不如循环引用的假设是唯一的原因还是有更多的原因?
ao218c7q1#
垃圾收集器使用跟踪或引用计数(epsilongc是一个不可回收的gc,两者都不使用,但是当它达到内存限制时,它就会爆炸。)通常,jvm使用跟踪引用采集器,偶尔会暂停stop-the-world,因为它对运行时的影响最小。引用计数的垃圾收集器更具可预测性,但在应用程序线程处理引用计数期间会产生运行时开销。值得注意的是,无论选择哪个垃圾收集器,它都会对代码生成产生不同的影响。例如,与多线程或多代垃圾收集器运行时相比,具有单线程垃圾收集器(例如在单个cpu上运行时)的jvm在分配引用时的开销可能更小。在这些情况下,可能需要标记卡或区域指针来突出显示需要检查引用的位置和方式。具体要求取决于垃圾收集器本身;例如,使用shenandoah和zgc生成的代码不同于使用g1gc或cms/parallel。所以有可能有一个使用引用计数的jvmgc;您可以增加或减少对象计数,而不是生成对卡片标记的引用。然而,参考计数GC有一个特殊的弱点,即它们不能检测周期。如果a指向b,b指向a,但在其他方面是不可访问的,那么如果使用纯引用计数策略,它们就不能被gc'd。使用引用计数的不同语言以不同的方式处理这个问题;例如,swift要求程序员确定一个“弱”引用,该引用将被破坏,从而允许清除循环。jvm没有(容易地)提供一种注解这种弱循环的方法,因此使用java的引用计数gc可能最终会泄漏这样的引用,或者它可能需要周期性的完整跟踪过程来逐出这样的循环(是的,您可以使用各种弱/虚引用,但它们本身几乎就是一种代码味道。)另一件事是,引用计数的gc在稳定状态下没有任何额外开销,因为一旦构建了对象图,就不需要再次访问它们。这可能会使对象的缓存位置更好。如果jvm进入无分配状态,那么gc不需要运行,但是年轻的gc可能会继续,并且还有其他周期性操作(有偏差的锁撤销等)可能会无意中触发gc。一般来说,尽管jvm可以进行调优,使其对象创建集仅在eden gen中,这是一个相当简单的操作,不需要定期重新扫描整个堆。如果你感兴趣,你可以给你自己的gc一个机会去发现。
1条答案
按热度按时间ao218c7q1#
垃圾收集器使用跟踪或引用计数(epsilongc是一个不可回收的gc,两者都不使用,但是当它达到内存限制时,它就会爆炸。)
通常,jvm使用跟踪引用采集器,偶尔会暂停stop-the-world,因为它对运行时的影响最小。引用计数的垃圾收集器更具可预测性,但在应用程序线程处理引用计数期间会产生运行时开销。
值得注意的是,无论选择哪个垃圾收集器,它都会对代码生成产生不同的影响。例如,与多线程或多代垃圾收集器运行时相比,具有单线程垃圾收集器(例如在单个cpu上运行时)的jvm在分配引用时的开销可能更小。在这些情况下,可能需要标记卡或区域指针来突出显示需要检查引用的位置和方式。具体要求取决于垃圾收集器本身;例如,使用shenandoah和zgc生成的代码不同于使用g1gc或cms/parallel。
所以有可能有一个使用引用计数的jvmgc;您可以增加或减少对象计数,而不是生成对卡片标记的引用。
然而,参考计数GC有一个特殊的弱点,即它们不能检测周期。如果a指向b,b指向a,但在其他方面是不可访问的,那么如果使用纯引用计数策略,它们就不能被gc'd。使用引用计数的不同语言以不同的方式处理这个问题;例如,swift要求程序员确定一个“弱”引用,该引用将被破坏,从而允许清除循环。
jvm没有(容易地)提供一种注解这种弱循环的方法,因此使用java的引用计数gc可能最终会泄漏这样的引用,或者它可能需要周期性的完整跟踪过程来逐出这样的循环(是的,您可以使用各种弱/虚引用,但它们本身几乎就是一种代码味道。)
另一件事是,引用计数的gc在稳定状态下没有任何额外开销,因为一旦构建了对象图,就不需要再次访问它们。这可能会使对象的缓存位置更好。
如果jvm进入无分配状态,那么gc不需要运行,但是年轻的gc可能会继续,并且还有其他周期性操作(有偏差的锁撤销等)可能会无意中触发gc。一般来说,尽管jvm可以进行调优,使其对象创建集仅在eden gen中,这是一个相当简单的操作,不需要定期重新扫描整个堆。
如果你感兴趣,你可以给你自己的gc一个机会去发现。