JVM 垃圾回收相关算法

什么是垃圾?没有任何引用指向的对象就是垃圾。

为什么要回收?伴随着程序的执行,垃圾越来越多,如果不回收将会导致可用的内存越来越少,直至抛出OOM(内存溢出)。

什么是内存泄漏?当一个对象不再使用,但是通过垃圾收集器又无法回收,就叫发生了内存泄漏。

标记阶段

引用计数器算法

        在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。

        优点:实现简单、垃圾对象方便辨识;判断效率高,回收没有延迟性(计数器为0就回收)

        缺点:1、占用额外的空间存储,增加空间开销;2、需要维护计数器的值,增加时间开销
·                   3、无法处理对象循环引用的问题(这是JVM垃圾收集器没有使用该算法的主要原因)

可达性分析算法

什么是可达性分析

        以根对象集合(GC Roots)为起始点,按照从上到下的方式搜索被根对象集合所连接的目标对象是否可达

        使用可达性分析算法之后,内存中存活的对象或直接或间接的都会与根对象相连,而搜索走过的路径称为引用链(Reference Chain)

        如果目标对象没有直接或间接与根对象相连,则不可达,就意味着该对象已经死亡,可以被标记为垃圾对象

        在可达性分析算法中,只有直接或间接与根对象相连的对象才是存活对象

 GC Roots是什么?

一系列必然活跃(存活)的对象

哪一些对象可以做为GC Roots?

  1. 在虚拟机栈(栈帧中的局部变量表)中被引用的对象
    1. 比如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等
  2. 在本地方法栈(JNT)中被引用的对象
  3. 方法区中类静态属性引用的对象
    1. Java类的引用类型静态变量
  4. 方法区常量引用的对象
    1. 字符串常量池(String Table)里的引用。
  5. 被同步锁(synchronized关键字)持有的对象
  6. Java虚拟机内部的引用
    1. 如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
  7. 反应Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
  8. 除了这些固定的GC Roots集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整GC Roots集合。譬如后文将会提到的分代收集和局部回收(Partial GC)

小技巧:由于Root采用栈方式存储对象和指针,如果一个指针保存了堆内存的对象,但自己又不保存在堆内存,那它就是一个Root

finalize方法

Object里的方法,可以被子类重写,在垃圾回收之前会调用。

对象的生存还是死亡?

        要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没
有与GC Roots相连接的引用链,那它将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为“没有必要执行”

不要主动调用某个对象的finalize()方法,应该交给垃圾回收机制调用,理由如下

  1. 在finalize()时,可能导致对象的复活
  2. finalize()方法的执行时间是没有保障的,完全由GC线程决定,极端情况下,如果不发生GC,那么finalize()方法将没有执行的机会
  3. 糟糕的finalize()方法(比如有死循环之类的)会严重影响GC的性能

三种可能的状态

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

  • 可触及的:根据GC Roots可以到达该对象
  • 可复活的:对象的所有引用被释放,但是可能在finalize()中被复活
  • 不可触及的:对象的finalize()被调用,且没有被复活。finalize()只能被调用一次

一般用于在对象和回收的时候进行资源的释放,通常在这个方法中进行一些资源的释放和清理工作,比如关闭文件、嵌套字和数据库连接等

finalize代码演示

清除阶段 

标记-清除算法

整个算法分成两个阶段,一个是标记阶段,另一个是清除阶段

标记:Collector(收集器)开始从根节点开始遍历,标记所有被引用的对象。在对象Header中标记为可达对象。在《深入理解Java虚拟机》中原话是:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。这一块我以宋红康老师的教程为主,认为标记是可达对象。

清除:Collector对堆内存从头到尾进行线性遍历,如果发现某个对象在Header中没有被标记为可达对象,则将其回收。

优点:最容易想到,也确实是最早出现的算法,后续的收集算法大多都以该算法为基础,针对其缺点改进。

缺点:1、效率比较低        2、会产生内存碎片,需要维护一个空闲列表来分配对象的内存

注意:这里的清除不是真的置空,而是把需要清除的对象地址保存到空闲的地址列表,在下一次需要分配内存的时候,判断垃圾所在位置的空间是否足够,如果够就直接覆盖

复制算法

采取空间换时间的策略,把内存区域一分为二。每次只使用其中的一块,如果这一块内存使用完了,就把所有还存活的对象复制到另一块内存区域,然后把已使用过的内存空间清空。

优点:效率高,没有内存碎片,可使用指针碰撞分配对象内存,不需要维护空闲列表

缺点:空间利用率不高,并且如果在有大量对象存活的情况下,效率急剧下降,需要复制大量对象

大多数虚拟机都采用这种算法去收集新生代,因为新生代的对象大多都是”朝生夕死“,每次需要复制的对象少,效率高。

标记-整理(压缩)算法

 解决了标记清除算法的内存碎片问题和复制算法空间浪费的问题,代价是其效率是最低的。

标记:和标记-清除算法一样,标记出所有被引用的对象

压缩:把所有存活的对象压缩到内存的一端,按顺序排放,然后清理边界外所有的空间

        标记-清除算法是一种非移动式算法,而标记-压缩是移动式算法。无论是否移动都有其弊端。如果不移动那么就会产生内存碎片,增加分配内存时的开销。如果移动那么内存回收会更复杂、停顿的时间更长。就整个程序的吞吐量来说,移动对象要更划算一些,因为内存的分配和访问比垃圾收集的频率要高的多,所以即使移动对象使得垃圾收集耗时长,但程序整体的吞吐量还是比不移动对象来的高。

三种收集算法对比

 

 分代收集算法

上面的三种收集算法各有各的特点,不同的对象生命周期是不一样的,为了针对不同的情况, 分代收集算法应运而生。一般把Java堆分成新生代和老年代,针对不一样的区域可以采用不一样的收集算法,以达到高效的的收集。比如新生代就适合复制算法,老年代就适合标记-压缩算法。

分代收集建立在两个分代假说之上:

  1. 弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
  2. 强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。
  3. 跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极少数。

都是对象并不是孤立存在的,新生代的对象也可能被老年代所引用,所以就有了第三条经验假说。举个例子,如果某个新生代对象存在跨代引用,由于老年代对象难以消亡,该引用会使得新生代对象在收集时同样得以存活,进而在年龄增长之后晋升到老年代中,这时跨代引用也随即被消除了。

增量收集算法

在现有的收集算法中,每次垃圾回收,应用程序都会处于一种Stop the World的状态,这种状态下,应用程序会被挂起,暂停一切正常的工作。这样一来,将严重影响用户体验或者系统稳定性。

基本思想:如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替进行。每次,垃圾回收线程只收集一小片区域的内存空间,接着切换到用户线程继续执行。依次反复,知道垃圾收集完成。

增量收集算法的基础仍然是传统的标记-清除和复制算法,通过对线程间冲突的妥善处理,垃圾收集线程以分阶段的方式完成标记、清除或复制工作

缺点:频繁的在垃圾收集线程和用户线程之后切换,会使得垃圾回收的成本增加、造成系统吞吐量的下降

分区收集算法

 一般来说,在相同的条件下,堆空间越大,一次GC所需要的时间就越长,STW的时间也就越长,为了更好的减少STW的时间,将一块大的内存区域分隔成多个小的区域,可以根据允许的停顿时间,每次合理的回收若干个小区域,而不是整个堆空间,从而减少STW

分代算法是把对象按照生命周期划分成两个部分,分区算法是将整个堆空间划分成连续的不同小区间Region.

每一个小区间都独立使用,独立回收。好处是可以控制每次回收多少个小区间,控制STW的时间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值