JVM垃圾回收机制-垃圾回收相关算法

1. 概述

       首先对比Java于C++语言在垃圾收集技术与内存动态分配上的区别,Java语言是提供自动垃圾回收功能的,C++没有自动垃圾回收,但垃圾回收也不是Java首创的(早在1960年,第一门开始使用内存动态分配和垃圾收集技术的 Lisp 语言诞生)。

       GC主要关注于堆中和方法区的垃圾收集(重点关注堆)。

1.1 什么是垃圾?

       垃圾是指在运行程序中没有任何引用指向的对象,这个对象就是需要被回收的垃圾。

1.2 为什么需要垃圾回收?

       若对内存中的垃圾清理不及时,这些垃圾对象所占的内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用,甚至可能会导致内存溢出(OOM)。

内存溢出: 内存不够用,报内存溢出错误OOM;

内存泄漏: 有些对象已经不再被使用了,但是垃圾回收对象又不能回收它,这样悄悄的占用着内存资源的现象。

       在早期像C++代码,需要程序员手动销毁垃圾对象,这样会增加程序员的工作量,并且若忘记删除,会导致内存泄漏,引进自动的内存管理降低了程序员的工作量,降低了内存溢出和内存泄漏的风险。

1.3 垃圾回收区域

       垃圾收集器可以对年轻代回收,也可对老年代回收,甚至是全栈和方法区的回收,重点关注堆。

       其从次数上讲:频繁收集Young区;较少收集Old区;基本不收集元空间(方法区)。

2. 垃圾回收算法

2.1 垃圾标记阶段算法

垃圾标记阶段: 主要是为了判断对象是否存货

(1)在堆里存放着几乎所有的Java对象实例,在GC执行垃圾回收之前,首先要区分出内存中的存活的对象和已经死亡的对象。只有被标记为已经死亡的对象,GC才会在执行垃圾回收时,释放掉其所占用的内存空间,其过程可称为垃圾标记阶段。

(2)在JVM中如何标记一个死亡对象?

       当一个对象已经不再被任何存活对象继续引用时,就可以宣判为已经死亡。

(3)判断对象存活一般有两种方式:引用计数算法和可达性分析算法。

2.1.1 引用计数算法

   是每个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况。

   该算法给每个对象分配一个计数器,当有引用指向这个对象时,计数器+1,当指向该对象的引用失效时,计数器-1。只要这个对象的引用计数器的值为0,即表示这个对象不可能再被使用,可进行回收。

优点:实现简单,垃圾对象便于辨识;判断效率高,回收没有延迟性。

缺点:每次引用对象时,都会更新计数器(伴随着加法和减法操作),有时间消耗;不能解决循环引用问题。

2.1.2可达性分析算法

可达性分析算法:也可称为根搜索算法,追踪性垃圾收集。

  可以理解为:一棵树有很多的分支,那么若其中一个分支断掉,那么它和树本身就木有了联系,那么我们就可以将其回收了。

可达性分析实现思路:

(1)可达性分析算法是通过一种被称作“GC Root”的对象作为起点,按照从上到下的方式搜索被根对象集合所连接的目标对象是否可达。

(2)使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或简介连接着,搜索所走过的路径称为引用链(Reference Chain)。

(3)如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象已经死亡,可以标记为垃圾对象。

(4)在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。

GC Roots可以是哪些元素?

(1)虚拟机栈中引用的对象(如:各个线程被调用的方法中使用到的参数、局部变量等);

(2)方法区中类静态属性引用的对象(如:Java类的引用类型静态变量);

(3)方法区中常量引用的对象(如:字符串常量池(StringTable)里的引用);

(4)本地方法栈内JNI(通常说的本地方法)引用的对象;

(5)所有被同步锁synchronized持有的对象;

(6)Java虚拟机内部的引用。

2.1.3 对象的finalization机制

   Java语言提供了对象终止(finalization)机制来允许开发人员提供对象被销毁之前的自定义处理逻辑

   当垃圾回收器发现没有引用指向一个对象,即:垃圾回收此对象之前,总会先调用这个对象的finalize()方法。

   finalize()方法允许在子类中被重写,用于在对象被回收时进行资源释放。通常在这个方法中进行一些资源释放和清理的工作,比如关闭文件、套接字和数据库连接等。

其Object类中finalize()源码:

永远不要主动调用某个对象的finalize()方法,应该交给垃圾回收机制调用。理由有以下三点:

  (1)在finalize()时可能会导致对象复活;

  (2)finalize()方法的执行时间是没有保障的,它完全由GC线程决定,极端情况下,若不发生GC,则finalize()方法将没有执行机会;

  (3)一个糟糕的finalize()会严重影响GC的性能。

在这抛出一个小问题:在进行垃圾回收时,被判断为不可达的对象还会被回收么?

即使是不可达的,对象也不一定会被回收:

 1)要先判断对象是否有必要执行finalize()方法,对象是必须重写finalize()方法并且没有被运行过;

  2)如果有必要去执行,我们可以放在队列中,JVM会开一个线程(Finalizer)去执行它。

由于finalize()方法的存在,虚拟机中的对象一般处于三种可能的状态:

可触及的:从根节点开始,可以到达这个对象。

可复活的:对象的所有引用都被释放,但是对象有可能在finalize()中复活。

不可触及的:对象的finalize()被调用,并且没有复活,那么就会进入不可触及状态。不可触及的对象不可能被复活,因为finalize()只会调用一次。

这三种状态中,是由于finalize()方法的存在,进行的区分。只有在对象不可触及时才会被回收。

2.2 垃圾回收阶段算法

   当在内存区分出存活的对象与死亡的对象以后,GC下面的任务就是执行垃圾回收,释放掉无用对象所占用的内存对象,以便有足够的可用空间为新对象进行内存分配。

2.2.1 复制算法

  使用到两块内存空间(对标两个幸存者区),将正在使用的区域中的存活对象,复制到另一个区间,排放整齐,清除原来的空间。

 优点:

  -没有标记和清除的过程,实现简单,运行高效;

  -内存碎片少

缺点:

   使用到两块内存,GC垃圾回收期每个区域又分成多个小的区域,需要记录地址。不论是内存占用或者是时间开销也不小。

2.2.2 标记-清除算法

   堆的内存区域分为一个个内存块,某个对象可能占用2个内存块,也可能占用若干个内存块,如果定位找到了垃圾对象,那么对垃圾对象进行标记,之后,在执行GC内存回收时,会将标记的内存回收。

    清除:不是真的置空(不是直接将垃圾对象清理),而是将垃圾对象的地址记录在一个空闲列表里面。下次有新对象需要加载时,判断垃圾的位置空间是否足够,如果足够就存放(也就是覆盖原有的地址)。

优点:

   -实现简单,不需要移动对象

缺点:

   -产生了很多不连续的内存(会产生内存碎片),如果对象比较大,会出现OOM

那么复制算法与标记-清除算法的区别是什么?

   复制算法是针对于新生代对象较少的情况,效率高,需要移动对象,不会产生内存碎片,但对于老年代对象较多的情况是不适应的。

   标记-清除算法是针对于老年代存活对象较多,不需要移动对象,但会产生内存碎片。

2.2.3 标记-压缩算法

   是针对于标记清除算法的不足,将存活的对象进行整理,然后清除掉垃圾对象,这样就不会产生内存碎片了。

优点:

   -消除了内存碎片

缺点:

   -效率低,低于复制算法;在移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值