浅谈JVM(一)之GC回收的算法

98 阅读3分钟

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

垃圾回收的主要方法

标记清除算法

  • 根据可达性算法标记出相应的可回收对象

  • 对标记的对象进行回收

  • 缺点也很明显:

    • 回收后,该内存坑坑洼洼的,没有办法获取一块连续的区域(内存碎片)

复制算法

  • 把堆等分成两块区域,区域A负责分配对象,区域B不分配
  • 把可存活的复制到B区域(依次紧邻排列),对整个A区域清除,解决了碎片问题
  • 缺点:分配内存,实际只有一半可用,并且每次回收需要移动可存活对象,效率堪忧

标记整理法

  • 标记与标记清除算法一样,标记后,先把存活对象向一端整理,然后清理掉边界以外的内存
  • 解决了内存碎片的问题
  • 每一次垃圾清除都需要频繁地移动存活对象,效率也很低下

分代收集算法

分代收集算法 整合了上述三种算法,结合了优点,尽量避开缺点 —— 这也是当代虚拟机采用的首选算法

关于对象存活周期

  • 绝大部分对象都是在很短的时间内被回收的,基本98%都熬不过第一次Minor GC,所以根据对象存活周期分为了新生代和老生代,比例是1:2(老生代大,最大限度避免 Full GC)
  • 新生代也分为三部分:Eden区、from survivor区、to survivor区;比例为8:1:1

实现

  • 新生代的三个区中,对象被放在Eden区,发生Minor GC,存活对象被放在S0区,并且对象的年龄+1;并且把Eden 清空(复制算法)
  • 再次发生GC 时,对象被存放在S1,清空Eden和S0,存活对象年龄+1(复制算法)
  • 再次发生GC时,对象又转移回S0,清空Eden和S1,存活对象年龄+1
  • 此时S0 和S1的定位并没有那么明显,他们互相切换位置,做一个中转筛子的作用
  • 因为8:1:1的比例,所以S0和S1的内存并不大,也减少了复制算法的效率低下
  • 直至存活对象的年龄阈值达到我们设置的(例如是15),将会晋升到老年代
  • 老年代因为大部分存活时间较长,所以会采用标记-整理算法

如何晋升到老年代

  1. 上述说的,年龄阈值到达设置值,就会晋升
  2. 大对象:需要分配大量的连续内存时 —— 因为是大量的连续内存,因为大内存对于Eden、S0、S1的复制算法,有很大的开销,所以会直接转移到老年代
  3. 当存在相同年龄和的对象超过S0/S1的空间一半时,大于或等于该年龄的会晋升,防止占据S区域过多内存

stop the world

stop the world 简称stw,也就是老年代满了以后,触发Full GC;只有垃圾回收线程在工作,其他工作线程会被挂起

  • 会导致工作线程停顿时间过长,因为Full GC会清理整个堆的不可用对象,如果当前收到请求,则会拒绝服务。

总结

为什么新生代需要两个区,S0、S1

因为:只有一个区的话会存在内存碎片,因为一个区满了就要去老年代

为什么新生代采用复制算法,老年代采用 标记-整理算法

因为:新生代比例是8:1:1,会较大程度规避复制算法的效率低下;而且新生代很多都是执行一次就被GC了,如果采用其他还要移动或者存在内存碎片,还不如直接复制;老年代因为对象存活久,占用的空间也大,所以采用标记-整理算法