免责声明:我绝对不是GC专家,但最近为了好玩而深入研究这些细节。
正如我在评论中所说,您正在使用一个已被弃用、没有人支持且没有人想使用的收集器,请切换到G1,甚至更好的是IMHO切换到Shenandoah:首先从这个简单的事情开始。
我只能假设您将ParGCCardsPerStrideChunk从其默认值增加,并且可能帮助了几毫秒(虽然我们没有证据)。我们也没有GC、CPU活动、日志等记录;因此这很复杂,无法回答。
如果确实有一个大堆(数十GB)和一个大的年轻空间,并且您有足够的GC线程,则将该参数设置为更大的值可能确实有所帮助,甚至可能与您提到的card table有关。请继续阅读原因。
CMS将堆分为旧空间和年轻空间,它可以选择任何其他区分符,但他们选择了年龄(就像G1一样)。为什么需要这样做?为了能够扫描和仅收集堆的部分区域(完全扫描非常昂贵)。年轻空间使用stop-the-world暂停来进行收集,因此最好保持较小,否则您将不会感到满意;这也是为什么通常会看到比旧集合更多的年轻集合的原因。
当您扫描年轻空间时唯一的问题是:如果从旧空间引用到年轻空间的对象有引用会发生什么?收集这些显然是错误的,但扫描整个旧空间以找出答案将完全破坏分代收集的目的。因此:card table。
这跟踪从旧空间到年轻空间引用的引用,因此它知道什么是垃圾或不是垃圾。G1也使用card table,但还添加了一个RememberedSet(这里不详细说明)。实际上,RememberedSets被证明是巨大的,这就是为什么G1变成了分代的原因。(FYI:Shenandoah使用矩阵而不是卡片表,使其不是分代的。)
所以这个长长的介绍是为了说明,增加
ParGCCardsPerStrideChunk
可能会有所帮助。这样可以给每个GC线程更多的工作空间。默认值为
256
,卡表为
512字节
,这意味着...
256 * 512 = 128KB per stride of old generation
如果你有一个堆大小为
32 GB
,那么这是多少个
数十万的步幅?可能太多了。
现在,为什么你要在这里讨论
引用计数
呢?我不知道。
你展示的例子具有不同的语义,因此很难进行推理;但我仍然会尝试。你必须理解对象的
可达性只是从一些根(称为
GC roots
)开始的图表。让我们先看看这个例子:
public void b(){
new ShortLivedObject().doSomething(new Object());
}
ShortLivedObject
实例在
doSomething
方法调用完成后被"遗忘",其
作用域仅限于该方法内部,没有其他途径可以访问到它。因此,剩下的部分涉及到
doSomething
方法的参数:
new Object
。如果
doSomething
不对它进行任何可疑的操作(使其通过
GC根
图成为可达对象),那么在
doSomething
完成之后,它也将变得可以被垃圾回收。但是,即使
doSomething
使
new Object
可达,这仍意味着
ShortLivedObject
实例也可以被垃圾回收。
因此,即使
Example
可达(表示它无法被回收),
ShortLivedObject
和
new Object()
可能被回收。代码示例如下:
new Object()
|
\ /
ShortLivedObject
|
\ /
GC Root -> ... - > Example
你可以看到,一旦
GC
扫描了
Example
实例,它可能根本不会扫描
ShortLivedObject
(这就是为什么垃圾被认为是与活动对象相反的)。因此,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
可能会有所帮助;但这是高度推测的,需要发生一种糟糕的分配模式才能实现所有这些。没有日志,我非常怀疑。
CMS
。你需要切换到G1
(甚至更好的是_Shendandoah_)并查看那里会发生什么。第二个问题是我怀疑你是否确实知道LongLivedObject
是一个真正的长期存在的对象 - 它是否被GC根引用?第三个问题是你混淆了很多术语:CMS
对于_年轻_代有一个STW暂停,对于老年代有两个短暂的暂停以及许多其他你混淆的事情。 - Eugene