Excel不相邻列如何打印在一起-英雄云拓展知识分享
102
2023-11-07
【摘要】 本书摘自《深入理解 Java 虚拟机 JVM 高级特性与最佳实践(第3版)》一书中第3章,第3节,周志明著。
3.3.2 标记-清除算法
最早出现也是最基础的垃圾收集算法是“标记-清除”(Mark-Sweep) 算法,在1960 年由Lisp 之父 John McCarthy 所提出。如它的名字一样,算法分为“标记”和“清除”两 个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象, 也可以反过来,标记存活的对象,统一回收所有未被标记的对象。标记过程就是对象是否 属于垃圾的判定过程,这在前一节讲述垃圾对象标记判定算法时其实已经介绍过了。
之所以说它是最基础的收集算法,是因为后续的收集算法大多都是以标记-清除算法 为基础,对其缺点进行改进而得到的。它的主要缺点有两个:第一个是执行效率不稳定, 如果Java 堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和 清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低;第二个是内 存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能 会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提 前触发另一次垃圾收集动作。标记-清除算法的执行过程如图3-2所示。
3.3.3 标记-复制算法
标记-复制算法常被简称为复制算法。为了解决标记-清除算法面对大量可回收对象时 执行效率低的问题,1969年Fenichel 提出了一种称为“半区复制” (Semispace Copying) 的 垃圾收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当 这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内 存空间一次清理掉。如果内存中多数对象都是存活的,这种算法将会产生大量的内存间复 制的开销,但对于多数对象都是可回收的情况,算法需要复制的就是占少数的存活对象, 而且每次都是针对整个半区进行内存回收,分配内存时也就不用考虑有空间碎片的复杂情 况,只要移动堆顶指针,按顺序分配即可。这样实现简单,运行高效,不过其缺陷也显而 易见,这种复制回收算法的代价是将可用内存缩小为了原来的一半,空间浪费未免太多了 一 点。标记-复制算法的执行过程如图3-3所示。
现在的商用Java 虚拟机大多都优先采用了这种收集算法去回收新生代,IBM 公司曾有一项专门研究对新生代“朝生夕灭”的特点做了更量化的诠释——新生代中的对象有98% 熬不过第一轮收集。因此并不需要按照1:1的比例来划分新生代的内存空间。
在1989年,Andrew Appel针对具备“朝生夕灭”特点的对象,提出了一种更优化的半 区复制分代策略,现在称为 “Appel 式回收”。HotSpot 虚拟机的 Serial 、ParNew 等新生代 收集器均采用了这种策略来设计新生代的内存布局°。Appel 式回收的具体做法是把新生代 分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次分配内存只使用 Eden 和其中 一块 Survivor 。发生垃圾搜集时,将 Eden 和 Survivor 中仍然存活的对象一次性复制到另外 一块 Survivor 空间上,然后直接清理掉 Eden 和已用过的那块 Survivor 空间。HotSpot 虚拟 机默认 Eden 和 Survivor 的大小比例是8:1,也即每次新生代中可用内存空间为整个新生代 容量的90% (Eden 的80%加上 一 个Survivor 的10%),只有 一 个 Survivor 空间,即10%的 新生代是会被“浪费”的。当然,98%的对象可被回收仅仅是“普通场景”下测得的数据, 任何人都没有办法百分百保证每次回收都只有不多于10%的对象存活,因此 Appel 式回收 还有一个充当罕见情况的“逃生门”的安全设计,当Survivor 空间不足以容纳一次 Minor GC 之后存活的对象时,就需要依赖其他内存区域(实际上大多就是老年代)进行分配担保 (Handle Promotion)。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们 18664393530@aliyun.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~