为什么需要GC?

如果不进行垃圾回收,内存耗空是迟早的。因为我们在不断的进行内存分配,而不进行垃圾回收。除非内存足够大,可以让我们随意分配内存。但事实并非如此。

什么是垃圾?

所谓垃圾就是指内存中已经没用的对象。那么我们如何找到这些没用的对象。JVM中使用一种叫做可行性分析的算法来决定对象是否要被回收。

可行性分析

这个算法的思想是通过一系列称为“GCRoot”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GCRoot没有任何引用链(即GCRoots到对象不可达)时,则证明此对象是不可用的。

image

如上图所示,对象A、B、C、D、E与GCRoot之间都存在一条直接或者间接的引用链,这也代表它们与 GC Root之间是可达的,因此它们是不能被GC回收掉的。而对象M和K虽然被对J 引用到,但是并不存在一条引用链连接它们与GCRoot,所以当GC进行垃圾回收时,只要遍历到 J、K、M 这 3 个对象,就会将它们回收。

注意:上图中圆形图标虽然标记的是对象,但实际上代表的是此对象在内存中的引用。包括 GC Root 也是一组引用而并非对象。

什么是GCRoot对象?

1、虚拟机栈(局部变量)引用的对象
2、方法区中常量引用的对象
3、方法去中静态引用的对象
4、本地方法栈中JNI(Native方法)引用的对象

什么时候GC?

1、在堆内存分配中,出现可用内存不足导致分配对象内存失败。系统主动GC。
2、在应用层,开发人员执行System.gc()主动执行GC操作。

如何进行垃圾回收?

主要通过如下几种方式进行垃圾回收:
1、标记清除算法
2、复制算法
3、标记压缩算法
4、分代回收算法

标记清除算法

标记清除是最基础的GC回收机制,字面意思,标记清除算法分为两步。
标记:找到内存中所有与GCRoot相连/间接性连接的对象标记为灰色(存活对象),否则标记为黑色(垃圾对象)。

清除:将垃圾对象直接清除。

  • 优点:实现简单,不需要将对象进行移动。
  • 缺点:标记清除后会产生大量不连续的内存碎片,内存碎片太多可能会导致以后程序运行过程中在需要分配较大对象时,无法找到足够的连续内存而不得不提前触发一次垃圾收集动作。

复制算法

将现有内存分为两块,每次只使用其一,当需要GC的时候,在内存中找到正在使用的对象,复制到另一块内存中,然后将当前内存直接清空。交换2个内存的角色。完成GC。

  • 优点:按序分配内存,无需考虑内存碎片。
  • 缺点:可用内存缩小为原来的一半,会存在频繁的GC操作。

标记压缩算法

过程和标记清除类似,但不是直接清除,而是将所有对象移动至内存的一端,然后回收那些不可用的对象。

  • 优点:这种回收算法既避免产生了内存的碎片,也不需要分两块内存空间,相对上面两种算是最优解了。
  • 缺点:所谓压缩,还是需要进行内存移动,一定程度上,降低了效率。

分代回收算法

JVM根据内存对象存活周期,把堆内存划分成了新生代,老年代,和持久代。分代回收的中心思想就是:对于新创建的对象会在新生代中分配内存,此区域的对象生命周期一般较短。如果经过多次回收仍然存活下来,则将它们转移到老年代中。

新生代

大批量死去的对象,少量的存活对象,一次GC能回收70%—95%的空间,回收效率极高。一般使用复制算法,复制成本低。

新生代又划分为一个Eden区和两个survivor(存活)区。这三部分按照8:1:1来划分新生代。
绝大多数新建的对象都被放在eden区。如图:
image
当eden区第一次满的时候回进行内存回收。将eden中的垃圾对象进行清除,将存活对象放至S0。此时S1是空的。如图:
image
下一次eden区满的时候,将eden区和S0中所有的垃圾对象清除,将存活对象放至S1,此时S0变为空。如图:
image
如此反复在S0和S1中切换(默认15次),依旧存活的对象放至老年代。

老年代

对象存活率高,没有额外空间进行分配,使用标记清除或者标记整理算法。

一个对象在新生代中存在过久没有被清除,此时就会将其移至老年代。老年代的内存空间会比新生代大很多。如果某个对象内存过大,导致无法将其放入新生代时,此对象会直接放入老年代中。

永久代

指内存中永久存在的数据,主要指class以及meta(元数据)信息。

四种引用

判断对象是否存活我们是通过GCRoots的引用可达性来判断的。但是JVM中的引用关系并不止一种,而是有四种,根据引用强度的由强到弱,他们分别是:强引用(StrongReference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)。

强引用 StrongReference

如果一个对象具有强引用,那么垃圾回收器绝对不会回收它,当内存不足时宁愿抛出 OOM 错误,使得程序异常停止。

Object object = new Object(); 即是一个强引用。

软引用 SoftReference

如果一个对象只具有软引用,那么垃圾回收器在内存充足的时候不会回收它,而在内存不足时会回收这些对象。软引用对象被回收后,Java虚拟机会把这个软引用加入到与之关联的引用队列中。

弱引用 WeakReference

如果一个对象只具有弱引用,那么垃圾回收器在扫描到该对象时,无论内存充足与否,都会回收该对象的内存。与软引用相同,弱引用对象被回收后,Java虚拟机会把这个弱引用加入到与之关联的引用队列中。

虚引用 PhantomReference

虚引用并不决定对象生命周期,如果一个对象只具有虚引用,那么它和没有任何引用一样,任何时候都可能被回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。与软引用和弱引用不同的是,虚引用必须关联一个引用队列。

参考

拉勾教育 - Android 工程师进阶34讲