• 垃圾收集算法


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

    1.1引用计数算法

    基本思路:

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

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

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

    1.2. 可达性分析算法

    又称为根可达性算法

    基本思路:

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

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

    例如:

    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,然后通知垃圾回收器进行回收:

    1. public class Demo01 {
    2. public static void main(String[] args) {
    3. //强引用
    4. String strongReference = new String("abc");
    5. String str = new String("just do do");
    6. //弱引用,进行保护,内存充足不回收。
    7. SoftReference softReference = new SoftReference<>(str);
    8. str = null;
    9. // Notify GC
    10. System.gc();
    11. try {
    12. byte[] buff1 = new byte[900000000]; // 内存充沛
    13. // byte[] buff2 = new byte[900000000];
    14. // byte[] buff3 = new byte[900000000];
    15. // byte[] buff4 = new byte[900000000]; // 内存不足
    16. } catch (Error e) {
    17. e.printStackTrace();
    18. }
    19. System.out.println(strongReference);
    20. System.out.println(softReference.get()); // just do do 或 null
    21. }
    22. }

    内存充足:

    内存不足:

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

    2.3. 弱引用(Weak Reference)

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

    创建弱引用,使用WeakReference

    1. public static void main(String[] args) {
    2. String str = new String("abc");
    3. WeakReference weakReference = new WeakReference<>(str);
    4. str = null;
    5. System.gc();
    6. // 一旦发生GC,弱引用一定会被回收
    7. System.out.println(weakReference.get());//结果为null
    8. }

    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. 内存空间碎片化问题:标记清除后会产生大量不连续的碎片,空间碎片太多,会导致分配较大对象时,无法找到足够的连续空间,从而会触发新的垃圾收集动作。

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

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

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

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

    存在的问题:

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

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

    实现思路:

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

    综上所述:

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

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

  • 相关阅读:
    13 英寸 MacBook Air 与 MacBook Pro 评比
    【Python Numpy教程】numpy数据类型
    单例的饥饿、懒汉模式案例
    128天创作纪念日
    复盘:智能座舱系列文六- 它的3种交互方式之显式交互(语音以及显示)
    Java三大特征之一——继承
    java计算机毕业设计ssm+vue个人时间规划系统
    idea 插件 checkstyle 规则示例和说明
    如何快速清理c盘缓存垃圾(最简单的c盘清理方法)
    iTOP2K1000开发板Makefile文件
  • 原文地址:https://blog.csdn.net/weixin_62221994/article/details/132859434