垃圾收集算法

1.如何判断对象是否存活?

1.1引用计数算法

基本思路:

  • 在对象中添加一个引用计数器
  • 每当有一个地方引用它的时候,计数器就加+1
  • 每当有一个引用失效的时候,计数器就减-1
  • 当计数器的值为0的时候,那么该对象就是可被GC回收的垃圾对象

存在的问题:对象循环引用

a 对象引用了 b 对象,b 对象也引用了 a 对象,ab 对象却没有再被其他对象所引用了,其实正常来说这两个对象已经是垃圾了,因为没有其他对象在使用了,但是计数器内的数值却不是 0,所以引用计数算法就无法回收它们。

垃圾收集算法_第1张图片

1.2. 可达性分析算法

又称为根可达性算法

基本思路:

过定义了一系列称为“GC Roots”的根对象作为起始节点集,从 GC Roots 开始,根据引用关系往下进行搜索,查找的路径我们把它称为 "引用链" 。当一个对象到 GC Roots之间没有任何引用链相连时(对象与GC Roots之间不可达),那么该对象就是可被GC回收的垃圾对象

可达性分析算法也是JVM 默认使用的寻找垃圾算法。

垃圾收集算法_第2张图片

例如:

Object 6Object 7Object 8彼此之前有引用关系,但是没有与"GC Roots"相连,那么就会被当做垃圾所回收。

2.Java 中的四种引用类型

 无论上述哪种算法,我们都可以看到引用在其中的重要作用。

2.1.强引用(Strong Reference)

强引用是使用最普遍的引用。如果一个对象具有强引用,垃圾回收器绝不会回收它。当内存空间不足时,JVM 即使抛出OOM错误,使程序异常终止,也不会随意回收具有强引用对象来解决内存不足的问题。

Object strongReference = new Object();

如果强引用对象不使用时,需要弱化从而使GC能够回收。

弱化方式1

显式地设置strongReference对象为nullgc认为该对象不存在引用,这时就可以回收这个对象。

注意:具体什么时候收集取决于GC算法, 例如,strongReference全局变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收。

strongReference = null;

弱化方式2:

让对象超出作用域范围。在一个方法的内部有一个强引用,这个引用保存在VM Stack中(GC Root),而真正的引用对象(Object)保存在中。当这个方法运行完成后,就会退出方法栈,则这个对象会被回收。

public void test() {
    Object strongReference = new Object();
    // 省略其他操作
}

2.2. 软引用(Soft Reference)

如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。所以,软引用可用来实现内存敏感的高速缓存。

// 软引用
String str = new String("abc");
SoftReference softReference = new SoftReference(str);

// 访问软引用
softReference.get();

 当内存不足时,JVM首先将软引用中的对象引用置为null,然后通知垃圾回收器进行回收:

public class Demo01 {
    public static void main(String[] args) {
        //强引用
        String strongReference = new String("abc");

        String str = new String("just do do");
        //弱引用,进行保护,内存充足不回收。
        SoftReference softReference = new SoftReference<>(str);
        str = null;

        // Notify GC
        System.gc();

        try {
            byte[] buff1 = new byte[900000000]; // 内存充沛
//            byte[] buff2 = new byte[900000000];
//            byte[] buff3 = new byte[900000000];
//            byte[] buff4 = new byte[900000000]; // 内存不足
        } catch (Error e) {
            e.printStackTrace();
        }
        System.out.println(strongReference);
        System.out.println(softReference.get()); // just do do 或 null
    }
}

内存充足:

垃圾收集算法_第3张图片

内存不足:

垃圾收集算法_第4张图片

 上述案例可以看出:强引用与软引用在内存不足时的回收情况。

2.3. 弱引用(Weak Reference)

只具有弱引用的对象拥有更短暂生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

创建弱引用,使用WeakReference

public static void main(String[] args) {
        String str = new String("abc");
        WeakReference weakReference = new WeakReference<>(str);
        str = null;

        System.gc();

        // 一旦发生GC,弱引用一定会被回收
        System.out.println(weakReference.get());//结果为null
    }

2.4. 虚引用(Phantom Reference)

虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,完全不会对其生存时间构成影响,它就和没有任何引用一样,随时可能会被回收。

虚引用,主要用来跟踪对象被垃圾回收的活动,可以在垃圾收集时收到一个系统通知。

3.垃圾收集算法

2.1. 分代收集理论

目前主流JVM虚拟机中的垃圾收集器,都遵循分代收集理论:

  • 弱分代:绝大多数对象都是朝生夕灭
  • 强分代:经历越多次垃圾收集过程的对象,越难以回收,难以消亡

        按照分代收集理论设计的“分代垃圾收集器”,所采用的设计原则:收集器应该将Java堆划分成不同的区域,然后将回收对象依据其年龄(年龄即对象经历过垃圾收集过程的次数)分配到不同的区域存储。

2.2.分代存储

如果一个区域中大多数对象都是朝生夕灭(新生代),难以熬过垃圾收集过程的话,把它们集中存储在一起,每次回收时,只关注如何保留少量存活对象,而不是去标记大量将要回收的对象,就能以较低代价回收到大量的空间。

如果一个区域中大多数对象都是难以回收(老年代),那么把它们集中放在一起,JVM虚拟机就可以使用较低的频率,来对这个区域进行回收。

2.3.分代收集

垃圾收集类型划分:

  • 部分收集(Partial GC):没有完整收集整个Java堆的垃圾收集,其中又分为:
    • 新生代收集(Minor GC / Young GC
    • 老年代收集(Major GC / Old GC
    • 混合收集(Mixed GC):收集整个新生代和部分老年代的垃圾收集。
  • 整堆收集(Full GC):收集整个Java堆的垃圾收集。

2.4.具体垃圾收集算法

Java堆区划分成不同区域后,垃圾收集器才可以针对不同的区域,安排与该区域存储对象存亡特征相匹配的垃圾收集算法:标记-复制算法标记-清除算法标记-整理算法等。

2.4.1.标记-清除算法( Mark-Sweep )

实现思路:

        分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。

存在的问题:

  1. 执行效率不稳定问题:如果执行垃圾收集的区域,大部分对象是需要被回收的,则需要大量的标记和清除动作,导致效率变低。
  2. 内存空间碎片化问题:标记清除后会产生大量不连续的碎片,空间碎片太多,会导致分配较大对象时,无法找到足够的连续空间,从而会触发新的垃圾收集动作。

 黑色:可回收对象      蓝色:存活对象       白色:未使用空间

垃圾收集算法_第5张图片垃圾收集算法_第6张图片

2.4.2.标记-复制算法 ( Copying )

        “标记-复制”收集算法简称“复制算法”,为了解决“标记-清除”面对大量可回收对象时执行效率低下的问题。

实现思路:
        该算法将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把已使用的空间一次清理掉。

存在的问题:

  1. 对象存活率较高,需要进行较多的内存间复制,效率降低
  2. 浪费过多的内存,使现有的可用空间变为原先的一半

垃圾收集算法_第7张图片垃圾收集算法_第8张图片

2.4.3. 标记-整理算法 ( Mark-Compact )

实现思路:

        标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行回收,而是让所有存活的对象向内存空间一端移动, 然后直接清理边界以外的内存,这样清理的机制,不会像标记-清除那样留下大量的内存碎片。

综上所述:

        当前虚拟机的垃圾收集都基于分代收集思想,根据对象存活周期的不同,将内存分为几个不同的区域,在不同的区域选择使用合适的垃圾收集算法

例如:Heap 堆分为新生代老年代,这样我们就可以根据各个年代的特点,从存活几率和额外的空间中选择合适的垃圾收集算法,

你可能感兴趣的:(jvm)