1.分代收集理论
1.1 两个假说
1
)弱分代假说(
Weak Generational Hypothesis
):绝大多数对象都是朝生夕灭的。
2
)强分代假说(
Strong Generational Hypothesis
):熬过越多次垃圾收集过程的对象就越难以消
亡。
1.2堆的分代
堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )
在新生代中,每次垃圾收集时都发现有大批对象死去,而每次回收后存活的少量对象,将会逐步晋升到老年代中存放。
困难:对象不是孤立的,对象之间会存在跨代引用。
1.3跨带引用说法
跨代引用相对于同代引用来说仅占极少数。存在互相引用关系的两个对象,是应该倾向于同时生存或者同时消亡的。举个例子,如果某个新生代对象存在跨代引用,由于老年代对象难以消亡,该引用会使得新生代对象在收集时同样得以存活,进而在年龄增长之后晋升到老年代中,这时跨代引用也随即被消除了。
只需在新生代上建立一个全局的数据结构,这个结构把老年代划分成若干小块,
(该结构被称为“
记忆集
”
,
Remembered Set)标识出老年代的哪一块内存会存在跨代引用跨代引用的小块内存里的对象才会被加入到GCRoots进行扫描。
虽然这种方法需要在对象改变引用关系(如将自己或者某个属性赋值)时维护记录数据的正确性,会增加一些运行时的开销,但比起收集时扫描整个老年代来说仍然是划算的。
2.标记清除算法
算法分为
“
标记
”
和
“
清除
”
两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。
缺点:
第一个是执行效率不稳定,如果Java
堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低;
第二个是内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
![](https://i-blog.csdnimg.cn/direct/d63f259b232e45d5b95390ff74bdd09a.png)
3.标记复制法
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
缺点:
将可用内存缩小为了原来的一半;
如果内存中多数对象都是存活的,这种算法将会产生大量的内存间复制的开销;
![](https://i-blog.csdnimg.cn/direct/3b7fde8bcc11416a920403d7747bf8b5.png)
4.标记整理算法
标记-
复制算法在对象存活率较高时就要进行较多的复制操作,效率将会降低。更关键的是,如果不想浪费50%
的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都
100%
存活的极端情况,所以在老年代一般不能直接选用这种算法。
其中的标记过程仍然与“
标记
-
清除
”
算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
![](https://i-blog.csdnimg.cn/direct/873333d734a541ecb60dd4395e6d2b00.png)
缺点:尤其是在老年代这种每次回收都有大量对象存活区域,移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行。
5.如何使用才会有效
但如果跟标记-
清除算法那样完全不考虑移动和整理存活对象的话,弥散于堆中的存活对象导致的空间碎片化问题就只能依赖更为复杂的内存分配器和内存访问器来解决。譬如通过“
分区空闲分配链表”
来解决内存分配问题(计算机硬盘存储大文件就不要求物理连续的磁盘空间,能够在碎片化的硬盘上存储和访问就是通过硬盘分区表实现的)。内存的访问是用户程序最频繁的操作,甚至都没有之一,假如在这个环节上增加了额外的负担,势必会直接影响应用程序的吞吐量。
基于以上两点,是否移动对象都存在弊端,移动则内存回收时会更复杂,不移动则内存分配时会更复杂。从垃圾收集的停顿时间来看,不移动对象停顿时间会更短,甚至可以不需要停顿,但是从整个程序的吞吐量来看,移动对象会更划算。此语境中,吞吐量的实质是赋值器(Mutator
,可以理解为使用垃圾收集的用户程序,本书为便于理解,多数地方用“
用户程序
”
或
“
用户线程
”
代替)与收集器的效率总和。即使不移动对象会使得收集器的效率提升一些,但因内存分配和访问相比垃圾收集频率要高得多,这部分的耗时增加,总吞吐量仍然是下降的。HotSpot
虚拟机里面关注吞吐量的
Parallel Scavenge收集器是基于标记
-
整理算法的,而关注延迟的
CMS
收集器则是基于标记
-
清除算法的,这也从侧面印证这点。
另外,还有一种“
和稀泥式”
解决方案可以不在内存分配和访问上增加太大额外负担,做法是让虚拟机平时多数时间都采用标记-
清除算法,暂时容忍内存碎片的存在,直到内存空间的碎片化程度已经大到影响对象分配时,再采用标记-
整理算法收集一次,以获得规整的内存空间。前面提到的基于标记-
清除算法的CMS
收集器面临空间碎片过多时采用的就是这种处理办法。