目录
Java中JVM的一个核心是垃圾回收机制。垃圾回收的 3 种算法:
- 标记-清除(Mark-Sweep)算法;
- 复制(coping)算法;
- 标记-整理(Mark-Compact)算法。
标记-清除(Mark-Sweep)算法
标记 - 清除(Mark-Sweep)算法是最基本的算法。就如同它的名字一样,分为“标记”和“清除”两个阶段:
- 首先标记出所有需要回收的对象,这就是标记阶段;
- 标记完成后统一回收所有被标记的对象,这就是所谓的清除阶段。
标记阶段也可以说为分析JVM内存中对象的可达性。将不可达的对象标记出来。等待后续的回收
JVM 的可达性分析法和四种引用_兜兜转转m的博客-CSDN博客
缺点:这种算法的不足主要体现在效率和空间。
- 从效率的角度讲:标记和清除两个过程的的效率都不高;
- 从空间的角度讲:标记清楚后会产生大量不连续的内存碎片,内存碎片太多可能会导致以后程序运行过程中在需要分配较大对象时,无法找到足够的连续内存而不得不提前触发一次垃圾回收动作
内存碎片:内存指的是堆内存,碎片指的是小的意思。内存碎片,就是不连续的小内存。这样小内存可能因为太小而不能被利用进行对象创建。
为了更加透彻的理解标记-清除(Mark-Sweep)算法,我们来看下如下示意图,通过直观的图形展示,彻底搞懂标记-清除(Mark-Sweep)算法---标记完哪些需要清除就直接进行清除。
复制(coping)算法
Tips:前文提到过,标记-清除(Mark-Sweep)算法从效率的角度讲,"标记"和"清除"两个过程的的效率都不高,为了提升效率,我们引出了复制(coping)算法。
复制算法是为了解决效率问题而出现的。
它将可用的内存分为两块,每次只用其中的一块
- 当这一块内存用完了,就将还存活着的对象复制到另外一块上面,
- 然后再把已经使用过的内存一次性清理掉。
- 这样每次只需要对整个半区进行内存回收,内存分配的执行过程如下图所示:
与标记清除相比,算法效率提升了,而且也不存在内存碎片问题。
但是牺牲了空间。其空间利用率只有一半。
tips 然后有研究发现。一般情况下新生代的98%都需要进行回收的。这也给复制算法提供了使用的场景。
- 因此新生代的内存被划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。
- 每次回收时,将Eden和Survivor中还存活着的对象一次性复制到另外一块Survivor空间上,最后清理掉Eden和刚才使用过的Survivor空间。
HotSpot虚拟机默认Eden区和Survivor区的比例为8:1,意思是每次新生代中可用内存空间为整个新生代容量的90%。当然,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖老年代进行分配担保(Handle Promotion)。
标记-整理(Mark-Compact)算法
Tips:复制算法在对象存活率较高的场景下要进行大量的复制操作,效率还是很低。并且每次只使用一半的内存空间,资源浪费严重。标记-整理(Mark-Compact)算法解决了内存利用率的问题,并且减少了大量复制的问题。
根据老年代的特点(这个算法常常用于清理老年代中的数据),有人提出了另外标记-整理(Mark-Compact)算法,标记过程与标记-整理(Mark-Compact)算法一样,不过不是直接对可回收对象进行整理,而是让所有存活对象都向一端移动,然后清理掉边界以外的内存。标记-整理算法的工作过程如图
那么网上经常提出的分代清理是怎么回事呢?
分代清理
分代收集理论结合了以上的 3 种算法,根据对象的生命周期的不同将内存划分为几块,然后根据各块的特点采用最适当的收集算法。准确的说,分代收集理论就是在不同的内存区域使用不同的算法,它是以上 3 种算法的使用者。
老年代一般常用标记整理算法,新生代使用标记清除和复制算法。