深入理解Java垃圾收集策略

目录

  • 概述
  • 判断对象是否存活
    • 可达性分析算法
    • 四大引用
  • 垃圾收集算法
    • 标记-清除算法
    • 标记-复制算法
    • 标记-整理算法
    • 对比
  • 总结

概述

垃圾收集(Garbage Collection,简称GC)机制是Java类语言中的一个强大优势,在通常情况下,我们只需关注对象的创建,无需关注什么时候回收。当本应被回收对象没有及时回收的现象成为内存泄漏,当因为内存空间不足够分配给新的对象时就会导致了内存溢出,导致程序出错。垃圾收集机制能够及时的回收死亡的对象,保证能够有足够的内存分配给新的对象。

Java运行时区域分为5个区域,其中程序计数器、虚拟机栈、本地方法栈的存亡与所在线程的存亡相同,堆区与方法区具有很大的不确定性,是垃圾收集的对象

垃圾收集过程是现需要判断对象是否需要回收,再使用垃圾收集器回收对象。

判断对象是否存活

可达性分析算法

当前主流程序语言的内存管理子系统是通过可达性分析算法判断对象是否存活,这个算法的基本思路是通过一系列根对象作为起始节点集,与根节点有直接或者间接引用关系的对象为仍然存活对象,若无引用关系则判定为可回收对象。

深入理解Java垃圾收集策略_第1张图片
在Java技术体系中可以作为GC Root的对象包括:

  • 在虚拟机栈中引用的对象。
  • 在方法区中静态属性引用的对象。
  • 在方法区中常量引用的对象。
  • 在本地方法栈中的JNI引用对象。
  • Java虚拟机内部的引用。
  • 被同步锁持有的对象。
  • 反应Java虚拟机内部情况的JMXBean、JVMTI注册的回调、本地代码缓存等。

四大引用

在判断对象是否需要回收是根据对象与对象之间存在引用关系,引用关系分强引用、软引用、弱引用、虚引用,引用强度依次递减。在不同场景下使用不同的强度的引用方式能够比较灵活的控制对象的回收时机。

  • 强引用: 强引用关系在任何情况下,垃圾收集器就永远不会回收到的对象。通过new方式就是强引用。
  • 软引用:用来描述一些还有用,但非必须的对象。可以通过get()方法获取到对象实例,被软引用关联的对象只有在内存不足,即将发生内存溢出之前才会进行回收。
  • 弱引用:用来描述哪些非必要对象。无法通过get()方法获取到对象实例,被弱引用关联的对象在系统垃圾回收器开始回收对象。
  • 虚引用:最弱的引用关系,无法通过get()方法获取到对象实例, 被虚引用关联的对象在系统垃圾回收器开始回收对象。
		//强引用对象 a
        Apple a = new Apple();

        //软引用对象 b
        SoftReference<Apple> b = new SoftReference<Apple>(new Apple());

        //弱引用对象 c
        WeakReference<Apple> c = new WeakReference<Apple>(new Apple());

        //虚引用 对象 d
        ReferenceQueue<Apple> queue = new ReferenceQueue<Apple>();
        PhantomReference<Apple> d = new PhantomReference<Apple>(new Apple(),queue);
        

垃圾收集算法

大多数垃圾收集器建立在“分代收集”收集理论之上,一般虚拟机会的Java堆划中至少包含新生代和老年代,“分代收集”建立在两个分代假说之上:

  • 弱分代假说:大多数对象都是朝生夕灭,即存活周期长度有限且固定的。
  • 强分代假说:熬过越多次垃圾收集过程的对象就越难消亡。

标记-清除算法

最早出现也是最基本的垃圾收集算法,先标记待回收对象,标记完成后,统一回收所有被标记的对象。

不足

  • 执行效率不稳定,当Java堆中包含大量对象,且大部分都待回收,这时候需要进行大量标记和清除动作,导致标记与清除两个过程的效率被大大降低。
  • 标记-清除方式回收后会产生大量不连续的内存碎片,空间碎片太多可能会增加后续的对象在分配内存的开销,当没有足够大连续空间为新对象分配内存还会再次触发垃圾回收。

标记-复制算法

将内存容量会分为大小相等的两块,每次都使用其中一块,当其中一块使用完,就将还活着的对象复制到另一块上面,然后再把自己使用过的内存空间一次清理掉。

不足

  • 当内存中大量的对象都是活的,这种算法会产生大量内存间复制的开销。
  • 这种算法将原本可用的内存缩小到原来的一般。

优化半区复制分代策略

因为堆中的对象绝大多数对象都在第一轮收集过程中被回收,满足“朝生夕灭”的特点。把新生代分为一块简答的Eden空间和两块Survivor空间,空间分配比率为8:1:1,每次分配内存支使用Eden和其中一块Survivor。发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另一块Survivor空间上,将Eden合原本的Survicor直接清理。这样有效的提高了空间的利用率。

标记-整理算法

标记-整理算法与标记-清除算法前面步骤是一样的,标记过程相同,在清除步骤前先让所有存活的对象向内存空间的一侧移动,然后直接清理掉待回收的对象。

不足
移动存活对象是一种极为负重的操作,在对象移动时必须全程暂停用户程序才能进行。

对比

标记-复制算法无法避免空间的浪费,但也有自己的一些优势。标记-清除与标记-整理过程主要区别是对象是否移动,移动内存时回收过程变得复杂,不移动则内存分配会更复杂,因为内存分配和访问相比较垃圾收集频率要高一些,所以移动对象总吞吐量是下降的。

总结

本文是对垃圾回收策略进行了阐述,介绍了判断对象是否存活算法——可达性分析算法与相关的引用关系、三种垃圾收集算法的并进行对比。

你可能感兴趣的:(JVM,读书笔记)