GC垃圾回收

485 阅读4分钟

这是我参与8月更文挑战的第26天,活动详情查看:8月更文挑战

WangScaler: 一个用心创作的作者。

声明:才疏学浅,如有错误,恳请指正。

我们java程序得以顺利的执行,GC功不可没。我们知道内存是有限的,我们在程序中创建的对象都会占用资源,而资源的释放就是通过GC垃圾回收来实现的。

判断对象是否可被回收

我们知道我们的对象没有被引用时,就可以被回收,那么我们怎么判断对象是否被引用呢?

  • 引用计数法:给对象增加一个引用计数器,每当有引用的时候,计数器加一;引用失效的时候,计数器减一。当计数器为0的时候,也就是无人引用的时候,将会将该对象回收。不过这种方法很少使用,因为它无法解决相互引用的问题。
  • 可达性分析:通过GC-Root向下搜索,不在链路上的对象将会被回收。在链路上的即可达,则认为是存活的对象。

可作为GC-Root的对象有:

  • 1、虚拟机栈中引用的对象。
  • 2、方法区中类静态属性引用的对象。
  • 3、方法区中常量引用的对象。
  • 4、本地方法栈中JNI(Native方法)引用的对象。

垃圾回收算法

这些算法都有各自的优势和劣势,所以应当使用在各自的应用场景,分代收集法由此而来。

  • 复制法:空间分为两个半区From和To,每次回收时都会将一个半区存活的对象复制到另一个半区,然后将整个半区进行回收。
  • 标记清除法:先标记要回收的对象,然后统一回收。会导致内存碎片,即内存区不连续。
  • 标记压缩法:扫描标记存活的对象向一端移动,从而重新组成一片连续的内存区域。需要移动内存对象的成本。
  • 分代收集法:新生代(大批对象死去、少量对象存活)使用复制算法,复制成本低;老年代(对象存活率高、没有额外空间进行分配担保的),采用标记-清理算法或者标记-整理算法。

复制法

image-20210826172607647.png

GC整体如上图所示,分为新生代和老年代。

  • 新生代:新生代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。分为Eden区和两个Survivor区。
  • 老年代:年老代主要存放JVM认为生命周期比较长的对象(在新生代经过几次GC之后还存活的对象)。

接下来,我们就来看看分代收集机制。

1、当对象创建的时候,就会将对象放进Eden区,直到Eden区满之后,就会触发GC,此时将Eden区中存活的对象通过复制算法复制到From区。

image-20210826173816721.png

此时将剩余的Eden区的对象进行回收。

2、当下一次Eden区满之后触发GC时,会将Eden区和From区存活的对象,复制到To区。将Eden区和From区剩余的对象进行回收。

image-20210826174226407.png

3、当下一次Eden区满之后再次触发GC,此时将Eden区和To区存活的对象,复制到From区。然后将Eden区和To区剩余的对象进行回收。

image-20210826174426344.png

4、上述的2、3步骤循环往复,直至From区或者To区到达阈值。此时在From区和To区来回游荡还一直存活的对象,将被复制到老年代。

image-20210826174922680.png

5、当老年代也到达阈值的时候,就会采用标记-清理算法或者标记-整理算法进行全量回收。此时除了垃圾回收的线程以外,其余的线程都将停止。

标记清除法

image-20210826180515080.png

如上图所示,红色表示存活的对象,蓝色表示有引用,但不是GC-Roots可达的对象,而灰色的表示已经不是存活的对象。那么除了红色存活的对象被标记之后不会被回收,其他的都将被回收。

image-20210826190455973.png

标记压缩法

image-20210826180515080.png

标记压缩法则是将存活的对象移动到内存边界,然后将剩下的空间进行回收。

image-20210826191023535.png

总结

以上三种方法结合起来就是分代收集法,今天的分享就到这里。

来都来了,点个赞再走呗!

关注WangScaler,祝你升职、加薪、不提桶!