这是我参与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),将会晋升到老年代
- 老年代因为大部分存活时间较长,所以会采用标记-整理算法
如何晋升到老年代
- 上述说的,年龄阈值到达设置值,就会晋升
- 大对象:需要分配大量的连续内存时 —— 因为是大量的连续内存,因为大内存对于Eden、S0、S1的复制算法,有很大的开销,所以会直接转移到老年代
- 当存在相同年龄和的对象超过S0/S1的空间一半时,大于或等于该年龄的会晋升,防止占据S区域过多内存
stop the world
stop the world 简称stw,也就是老年代满了以后,触发Full GC;只有垃圾回收线程在工作,其他工作线程会被挂起
- 会导致工作线程停顿时间过长,因为Full GC会清理整个堆的不可用对象,如果当前收到请求,则会拒绝服务。
总结
为什么新生代需要两个区,S0、S1
因为:只有一个区的话会存在内存碎片,因为一个区满了就要去老年代
为什么新生代采用复制算法,老年代采用 标记-整理算法
因为:新生代比例是8:1:1,会较大程度规避复制算法的效率低下;而且新生代很多都是执行一次就被GC了,如果采用其他还要移动或者存在内存碎片,还不如直接复制;老年代因为对象存活久,占用的空间也大,所以采用标记-整理算法