可以增加gc time引用了旧对象的短命对象吗?

nac*_*okk 5 java garbage-collection jvm concurrent-mark-sweep java-8

我需要一些关于次要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
    }

}
Run Code Online (Sandbox Code Playgroud)

我的疑惑来自何处?我发现,在使用的使用权空间变大的应用程序中,gc暂停次数增加了。

做一些测试,我发现如果我强迫jvm使用option,a()而另一个jvm使用option b(),则b()当旧空间变大时,带有option的jvm的暂停持续时间较短,但我不知道为什么。

gc cpu的使用时间

我使用4096中的此属性XX:ParGCCardsPerStrideChunk在应用程序中解决了该问题,但我想知道上述情况是否会导致gctimes增大,从而导致gccard表中的扫描速度变慢或我不知道或正在完全没有关系。

Eug*_*ene 2

免责声明:到目前为止,我还不是 GC 专家,但最近出于兴趣而深入研究这些细节。

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

我只能假设您从默认值开始增加 ParGCCardsPerStrideChunk,这可能会有所帮助ms(尽管我们没有证据证明这一点)。我们也没有来自 GC、CPU 活动、日志等的日志;因此,这个问题回答起来相当复杂。

如果您确实有一个大堆(数十 GB)一个大的年轻空间,并且您有足够的 GC 线程,那么将该参数设置为更大的值可能确实有帮助,甚至可能card table与您提到的有关。进一步阅读原因。

CMS将堆分成 和old spaceyoung space它可以选择任何其他判别器,但他们选择了age(就像G1)。为什么需要这个?能够仅扫描和收集堆的部分区域(整个扫描非常昂贵)。young space收集时有stop-the-world停顿,所以最好小一点,否则你会不高兴;这就是为什么你通常会看到更多的young collections比较old ones

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

这会跟踪引用之间的引用old spaceyoung space因此它知道到底什么是垃圾。G1也使用了 a card table,但还添加了 a RememberedSet(这里不再赘述)。实际上,RememberedSets事实证明是巨大的,这就是为什么G1成为一代人的原因。(仅供参考:Shenandoah使用matrix而不是card table- 使其不分代)。

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

256 * 512 = 128KB per stride of old generation
Run Code Online (Sandbox Code Playgroud)

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

那么,为什么你也在reference counting这里讨论呢?我不知道。


您所展示的示例具有不同的语义,因此很难推理;不过我仍然会尝试。您必须了解对象的可达性只是一个从某些(称为GC roots)开始的图。我们先来看这个例子:

public void b(){
   new ShortLivedObject().doSomething(new Object()); // actually now is shortlived
}
Run Code Online (Sandbox Code Playgroud)

ShortLivedObject一旦doSomething方法调用完成,实例就会被“遗忘”,并且它的范围仅在方法内,因此没有人可以访问它。doSomething因此剩下的部分是关于:的参数new Object。如果doSomething不对它获得的参数做任何可疑的事情(使其可以通过图表到达GC root),那么doSomething完成后,它也将有资格进行 GC。但即使doSomething使其new Object可达,它仍然意味着该ShortLivedObject实例有资格进行 GC。

因此,即使Example 可达的(意味着它无法被收集),ShortLivedObject并且new Object() 可能被收集。它可以看起来像这样:

                 new Object()
                      |
                     \ /
               ShortLivedObject           
                      |
                     \ /
GC Root -> ... - > Example
Run Code Online (Sandbox Code Playgroud)

您可以看到,一旦GC扫描Example实例,它可能根本不会扫描(这就是为什么垃圾被识别为与活动对象相反的原因)。因此 GC 算法将简单地丢弃整个图并且根本不扫描它。ShortLivedObject


第二个例子有所不同:

public void a(){
    var shortLived = new ShortLivedObject(longLived);
    shortLived.doSomething();
}
Run Code Online (Sandbox Code Playgroud)

不同之处在于,longLived这是一个实例字段,因此,图表看起来会有点不同:

                ShortLivedObject
                      |
                     \ /
                  longLived         
                     / \
                      |
GC Root -> ... - > Example
Run Code Online (Sandbox Code Playgroud)

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

Example你必须明白,如果可以收集实例,这根本不重要;该图不会被遍历,并且Example可以收集使用的所有内容。

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