java - 可以增加引用旧对象的 gc 时间短命对象吗?

标签 java java-8 garbage-collection jvm concurrent-mark-sweep

我需要一些关于次要 gc 集合的行为的说明。调用 a()或调用 b()在一个长期存在的应用程序中,如果它们在旧空间变大时表现最差

//an example instance lives all application life cycle 24x7
public class Example {

    private Object longLived = new Object(); 

    public void a(){
        var shortLived = new ShortLivedObject(longLived); // longLived now is attribute
        shortLived.doSomething();
    }


    public void b(){
       new ShortLivedObject().doSomething(new Object()); // actually now is shortlived
    }

}

我的疑惑从何而来?我发现在使用的终身空间变大的应用程序中,轻微的 gc 暂停会增加。

进行了一些测试,我发现如果我强制 jvm 使用选项 a()和另一个 jvm 使用选项 b() ,然后是带有选项 b() 的 jvm当旧空间变大时,暂停持续时间更短,但我不知道为什么。

gc cpu utilization time

我在应用程序中解决了这个问题,使用这个属性 XX:ParGCcardsPerStrideChunk 在 4096 中,但我想知道我上面描述的情况是否会导致 gctime 增加,导致 gccard 表中的扫描速度变慢,或者我不知道或根本不相关的东西。

最佳答案

免责声明:我目前还不是 GC 专家,但最近为了好玩而深入了解这些细节。

正如我在评论中所说,您正在使用不推荐使用的收集器,没有人支持它,也没有人想使用它,切换到 G1或者更好的恕我直言切换到 Shenandoah : 首先从这个简单的事情开始。

我只能假设你增加了ParGCCardsPerStrideChunk从它的默认值开始,这可能有助于一些 ms (虽然我们没有证据证明这一点)。我们也没有来自 GC、CPU Activity 、日志等的日志;因此,回答这个问题非常复杂。

如果确实有一个很大的堆(几十 GB)和一个很大的年轻空间,并且有足够的 GC 线程,那么将该参数设置为更大的值可能确实有帮助,甚至可能与 card table 有关。你提到的。进一步阅读原因。
CMS将堆拆分为 old spaceyoung space ,它可以选择任何其他鉴别器,但他们选择了 age (就像 G1 )。为什么需要这样?能够仅扫描和收集堆的部分区域(完全扫描它非常昂贵)。 young spacestop-the-world 收集暂停,所以最好小一点,否则你不会快乐;这也是为什么您通常会看到更多 young collections比较 old ones .

扫描时唯一的问题young space是:如果有来自 old space 的引用会发生什么到来自 young space 的对象?收集那些显然是错误的,但扫描整个old space找出答案会违背 generational collections 的目的完全。因此:card table .

这会跟踪来自 old space 的引用至 young space引用,所以它知道到底什么是垃圾。 G1使用 card table也是,而且还增加了一个 RememberedSet (此处不详述)。在实践中,RememberedSets结果是巨大的,这就是为什么G1成为一代人。 (仅供引用:Shenandoah 使用 matrix 而不是 card table - 使其不分代)。

所以这个巨大的介绍,是为了表明确实增加了ParGCCardsPerStrideChunk可能有帮助。您给每个 GC 线程更多的工作空间。默认值为 256和卡片表是512 bytes ,这意味着

256 * 512 = 128KB per stride of old generation

例如,如果您有一堆 32 GB那是多少十万步?可能太多了。

现在,你为什么还要带reference counting进入这里讨论?我不知道。

您展示的示例具有不同的语义,因此有点难以推理;不过我还是会努力的。你必须明白,对象的可达性只是一个从一些根开始的图(称为 GC roots)。我们先来看这个例子:
public void b(){
   new ShortLivedObject().doSomething(new Object()); // actually now is shortlived
}
ShortLivedObject实例尽快被“遗忘” doSomething方法调用已完成,其范围仅在方法内,因此没有人可以访问它。因此剩下的部分是关于doSomething的参数:new Object .如 doSomething对它得到的参数不做任何可疑的事情(使它可以通过 GC root 图形访问),然后在 doSomething 之后完成后,它也将有资格进行 GC。但即使 doSomething使 new Object可达它仍然意味着ShortLivedObject实例符合 GC 条件。

因此,即使 Example可达(表示无法收集),ShortLivedObjectnew Object()有可能被收集。它看起来像这样:
                 new Object()
                      |
                     \ /
               ShortLivedObject           
                      |
                     \ /
GC Root -> ... - > Example

你可以看到一次GC将扫描 Example例如,它可能不会扫描 ShortLivedObject根本没有(这就是为什么垃圾被识别为 Activity 对象的对立面)。因此,GC 算法将简单地丢弃整个图,而根本不扫描它。

第二个例子不同:
public void a(){
    var shortLived = new ShortLivedObject(longLived);
    shortLived.doSomething();
}

不同的是longLived这是一个实例字段,因此,图表看起来有点不同:
                ShortLivedObject
                      |
                     \ /
                  longLived         
                     / \
                      |
GC Root -> ... - > Example

很明显ShortLivedObject在这种情况下可以收集,但不能收集 longLived .

如果Example,你必须明白这根本不重要可以收集实例;此图将不会被遍历和所有 Example可以收集使用。

您现在应该可以理解使用方法 a可以保留更多垃圾并可能将其移至 old space (当他们变得足够老时)并可能使您的 young pauses更长,确实在增加 ParGCCardsPerStrideChunk可能会有所帮助;但是这个高度投机并且您需要一个非常糟糕的分配模式才能发生所有这些。没有日志,我非常怀疑。

关于java - 可以增加引用旧对象的 gc 时间短命对象吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58924737/

相关文章:

java - 有什么方法可以流式传输像 "(k,v)"这样的 map 而不是使用(条目)?

java - 是否有一个简单的类封装了一个 boolean 值和一个字符串?

java - spring-boot web 应用程序中未考虑 web.xml session 超时?

java-8 - 为什么@FunctionalInterface 不能应用于 SAM 抽象基类

javascript - JS 垃圾回收

java - 使用 String IV 进行 AES 加密

java - 如何使用java8流结合grouping by语句计算值的总和

spring - PSQLException - spring boot 1.4.1 - spring data jpa - offsetdatetime/localdatetime 标识为字节流

java - 暴露应用程序是否正在通过 UDP 进行 GC

java - 字符串对象及其文字是否被垃圾收集?