并行压缩收集器算法

4
我有两个问题,其中一个涉及以下主题:
1)我遇到了一个问题,即无法找到关于HotSpot中不同垃圾回收器如何工作的全部信息。但我不是在谈论垃圾回收器工作的一般描述(我们在互联网上有很多这方面的信息),我是在谈论具体的算法。我找到了这篇白皮书(Java HotSpot虚拟机中的内存管理)http://www.oracle.com/technetwork/java/javase/tech/memorymanagement-whitepaper-1-150020.pdf。 但它只有一般想法。它对并行紧凑算法(我的意思是并行标记-清除-紧缩)进行了很好的描述(可能不太好,请参见我的第二个问题),但它没有解释其他垃圾回收器的算法。然而,这篇白皮书是我能在互联网上找到的最好的信息。我想知道的是,在哪里可以获取有关不同垃圾收集器(对于年轻代,我指的是:ParNew,DefNew,PSYoungGen; 对于老年代:PSOLdGen,ParOldGen,Concurrent-Mark-Sweep)如何工作的完整说明/信息。无法相信这些信息不可用于用户。
2)关于并行压实收集器算法(ParOldGen或Parallel Mark-Sweep-Compact)的问题。白皮书(见第一个问题)对其工作进行了描述。让我引用一句话(请花一分钟看一下):
有以下我无法理解的内容:
关于总结阶段:
由于之前的收集而发生压缩,通常情况下每个代的左侧某些部分将是密集的,包含大多数活动对象。从这样的密集区域中可以回收的空间量不值得对它们进行压缩的成本。
这是否意味着当我们有一个由98-99%的活动对象和2-1%的死对象组成的区域时(换句话说是非常小的死对象比例),则该区域的压缩不值得从该区域中回收的空间。但是,这些微小的空闲空间(孔)最终将被填满,并且在垃圾收集完成后将没有孔。
因此,总结阶段首先要做的事情是检查区域的密度,从最左边的区域开始,直到达到从该区域及其右侧区域中可以回收的空间值得对这些区域进行压缩的点。
那么,如果我们有大量的死对象,那么这个区域就值得压缩了,对吗?
  • 左边的区域被称为密集前缀,这些区域中不会移动对象。

“这些区域中不会移动对象”,但是这些区域可能有一些小的空闲空间,我说得对吗?不能理解重点。

  • 右边的区域将被压缩,消除所有死空间。

请澄清它们将如何被压缩。每个区域都会单独压缩吗?我猜不会,那么这里可能会有一些移位操作吗?

  • 总结阶段计算并存储每个压缩区的活动数据第一个字节的新位置。

为了理解这一点,我需要理解上一个问题。

关于压缩阶段

  • 在压缩阶段,垃圾回收线程使用总结数据来识别需要填充的区域,线程可以独立地将数据复制到这些区域。这产生了一个紧密打包在一端的堆,另一端有一个单独的大空块。

我完全困惑了。所以“总结阶段”没有进行压缩?前一个阶段的目的只是找到所有的空闲空间吗?

请帮助我获得清晰的图片。


2
我不知道它们是如何工作的,也没有亲自尝试过,但你可以阅读OpenJDK中垃圾回收器代码,这可能会对你有所帮助。 - tmyklebu
1
我猜甚至维护这些东西的人也不知道它是如何工作的,它太复杂(而且易变)了,无法撰写一本准确描述每个算法细微差别的书籍。我怀疑你正在寻找比可用的更多细节。 - Hot Licks
1
然而,这些微小的空闲空间(洞)最终会被填满,在垃圾回收完成后就不会再有洞了,但我对此理解并不是这样。Hotspot不是传统的堆。 "洞"不会被填充。相反,我们需要等待整个区域被压缩。对于大多数Hotspot区域,“压缩”意味着将所有对象从旧空间复制到新空间,跳过“洞”。 Hotspot在很大程度上是一种“停止和复制”收集器。 - Hot Licks
1
有两种基本不同的GC形式:“ 停止和复制”和“空闲列表”。 “空闲列表”版本有点像传统的堆管理器,其中释放的分配被添加到列表中以供重新分配。 另一方面,“停止和复制”会从区域A物理地复制对象到区域B,跳过“空洞”,然后在现在空置的区域A中开始分配新对象。 HotSpot在很大程度上是“停止和复制”的。 - Hot Licks
2个回答

4
这只是算法的通用描述。这样的描述可以有不同的细节。在这种情况下,它给出了大多数详细信息,但仍为实现者留下了一些选择余地。
关于你的问题:
1. “所以“摘要阶段”没有发生压实吗?前一个阶段的目的只是找到所有的空闲空间吗?” - 是的,这是正确的。摘要阶段收集索引数据并基本上确定了所有必要的内容,以便压实阶段可以执行复制。他们没有说他们如何实现压缩,但默认方式是将每个活动对象放在前一个对象旁边。基本上,所有的空间都被移除,在压实步骤完成后,您将得到一个包含所有活动对象的连续内存块。我看到了您对第四部分的困惑,但请注意它是用将来时写的:“将被压缩”- 因此不是在摘要阶段,而是在之后。
2. “这是否意味着[...]对该区域进行压缩不值得从这样的区域中恢复的空间?” 是的,这是正确的。您实际上会失去一些空间,但为了执行速度,牺牲内存很常见。确切的密度阈值取决于实现方式,但我会将用于总内存比例阈值大致估算为70-90%。
如果您想了解所有肮脏的细节,请查看评论中建议的开源VM实现。

2
如果你真的需要详细了解收集器的工作原理,可以阅读代码。之所以找不到许多详细页面是因为收集器旨在为您管理内存,如果您开始担心细节,那么您已经走错了路。
最好的解决方案是使用内存分析器并减少分配速率。无论您进行多少调整或更改命令行选项(除非您的GC配置错误),都无法与减少此分配速率相比。
然而,回答您的问题。
并行标记-清除-压缩
这种东西不存在。有并行收集器可以压缩和并发标记清除收集器不能压缩。还有一个G1收集器,它的代际方式不同。即它同时收集年轻对象和老对象。
无法相信这些信息对用户没有用。
按设计,开发人员不需要知道这么多细节。过度调整应用程序也不是一个好主意,因为这会使其对应用程序或JVM的更改非常脆弱。
我想知道的是在哪里可以获取关于不同垃圾收集器的完整描述/信息。
与其说“我想知道所有要知道的内容(光是选项就有500多个)而不必阅读代码”,不如尝试解决特定问题并提出具体问题。
每个区域都会单独压缩吗?我想不会。所以这里可能会有某种移位?
只有老年代空间会被压缩。年轻的区域会反复复制,从不需要压缩。
因此,“摘要阶段”没有发生压缩吗?上一个阶段的目的只是找到所有的空闲空间吗?
压缩阶段会尽最大努力将对象复制到区域的一端,而不完全进行碎片整理。这样会在一端留下一些对象(主要是大对象),在另一端非常密集。

从设计上来说,开发人员不需要知道这么多细节。他们只需要关注Java程序的形式语义,而不是那些无法被形式语义捕捉到的现实世界效果(时间和空间消耗)。 - tmyklebu
实际上,他们需要有一些想法,但如果您需要确切地了解压缩的工作原理,则我认为您做错了什么。通常,在堆中添加几个GB就可以解决这个问题,在长期来看更便宜(1 GB的成本为3美元),并且需要的时间更少。 - Peter Lawrey
2
了解某个东西的工作原理可以让你设计出其强项。 “只需购买更多RAM”并不总是一个选项;现代主板有非常有限的能力接受内存条并将其利用起来。扩大堆大小可能会使GC暂停时间变长,从而对延迟产生不利影响。(尽管我同意,如果这种细节会决定你的项目成败,并且你不会远离GC环境,那么你就处于一种深度的罪恶状态。) - tmyklebu
你能否请看一下我的另一个问题https://dev59.com/F3zaa4cB1Zd3GeqPRY62,也许你能帮我。 - Anton Kasianchuk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接