被说烂了的Java垃圾回收算法,我带来了最“清新脱俗”的详细图解( 二 )


标记-复制算法将内存分为大小相同的两个区域,运行区域,预留区域,所有创建的新对象都分配到运行区域,当运行区域内存不够时,将运作区域中存活对象全部复制到预留区域,然后再清空整个运行区域内存,这时两块区域的角色也发生了变化,每次存活的对象就像皮球一下在运行区域与预留区域踢来踢出,而垃圾对象会随着整个区域内存的清空而释放掉,内存前后的状态参考下图:

被说烂了的Java垃圾回收算法,我带来了最“清新脱俗”的详细图解

文章插图
?标记-复制算法回收前后内存对比
标记-复制算法在大量垃圾对象的情况下,只需复制少量的存活对象,并且不会产生内存碎片问题,新内存的分配只需要移动堆顶指针顺序分配即可,很好的兼顾了效率与内存碎片的问题 。
标注-复制算法也存在缺点
预留一半的内存区域未免有些浪费了,并且如果内存中大量的是存活状态,只有少量的垃圾对象,收集器要执行更多次的复制操作才能释放少量的内存空间,得不偿失 。
 
3.3 标记-整理算法标记-复制算法要浪费一半内存空间,且在大多数状态为存活状态时使用效率会很低,针对这一情况计算机科学家又提出了一种新的算法“标记-整理算法”,标记整理算法的标记阶段与其他算法一样,但是在整理阶段,算法将存活的对象向内存空间的一端移动,然后将存活对象边界以外的空间全部清空,如下图所示:
被说烂了的Java垃圾回收算法,我带来了最“清新脱俗”的详细图解

文章插图
?标记-整理算法回收前后内存对比
标记整理算法解决了内存碎片问题,也不存在空间的浪费问题,看上去挺美好的 。但是,当内存中存活对象多,并且都是一些微小对象,而垃圾对象少时,要移动大量的存活对象才能换取少量的内存空间 。
不同的垃圾回收算法都有各自的优缺点,适应于不同的垃圾回收场景
 
四、新生代、老年代堆内存结构Java 堆内存空间新生代、老年代是如何划分的?对象创建后是如何分配到不同的区域的?结合下图可以知道,整个堆内存被分为了2个大的区域,新生代,老年代,默认情况下新生代占1/3的空间,老年代占2/3的空间,新生代又分为两个区 Eden区Survial区,Survial又分为S0、S1区 默认各占8/10与1/10,1/10的空间 。
被说烂了的Java垃圾回收算法,我带来了最“清新脱俗”的详细图解

文章插图
?年轻代 老年代 堆空间结构
为什么要这么设计呢?为什么要分那么多不同的内存区域干嘛?这是由对象的生命周期特征、与各类垃圾回收算法的优缺点所决定的,这正式垃圾回收器设计的理论基础 。经过统计分析,大多数应用程序对象生命周期符合两个特征:
垃圾回收的理论基础
  • 绝大多数的对象都是“朝生夕灭”的,既创建不久即可消亡;
  • 熬过越多此垃圾回收过程的对象就越难以消亡;
一块独立的内存区域只能使用一种回收算法,根据对象生命周期特征,将其划分到不同的区域,再对特定区域使用特定的垃圾回收算法,只有这样才能将垃圾算法的优点发挥到极致,这种组合的垃圾回收算法叫:分代垃圾算法 。。比如:在新生代使用标记-复制算法,在老年代使用标记-整理算法 。
五、堆内存回收过程详解我们分析了如何判断对象是否可回收,还有3中基础的垃圾回收算法,以及年轻代、老年代的内存区域划分与原因 。接下来我们就一步一步来分析堆内存的回收流程 。
5.1 内存初始状态假设在第一垃圾回收之前,内存中的状态如图所示,Eden区有2个存活对象,3个垃圾对象,内存的可用区域已经所剩无几,Survivor区因为还没有进行任何MinorGC所以是空的,有1个大对象直接分配到了老年代,
被说烂了的Java垃圾回收算法,我带来了最“清新脱俗”的详细图解

文章插图
垃圾回收初始状态
5.2 第1次执行MinorGC后状态当新的对象分配到Eden区,发现内存空间不够,于是触发第一次MinorGC,垃圾回收器首先将Edne区中的两个存活对象复制到S0区,然后在清空Eden区的空间,如下图:
被说烂了的Java垃圾回收算法,我带来了最“清新脱俗”的详细图解

文章插图
?第一次MinorGC内存状态
5.3 程序运行一段时间后状态经过第1次MinorGC程序再运行一段时间后,堆内存状态如下:Eden区又产生了大量的对象,并且大部分对象都可回收状态,这也符合对象“朝生夕灭”的特征,S0区中也有1个对象可以回收,S1与老年代没有变化,在这种状态下,如果新对象分配再次触发MinorGC会发生什么呢?


推荐阅读