Java虚拟机垃圾回收相关的算法

垃圾回收概述

什么是垃圾

运行程序中没有任何指针指向的对象

为什么要垃圾回收

对内存进行回收,避免内存耗尽。垃圾回收的同时可以对内存碎片进行整理。

早期垃圾回收

在C,C++中,内存需要程序员使用完后手动回收。

Java垃圾回收机制

内存回收由垃圾回收器负责,无需程序员参与。

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

频繁回收年轻代,较少回收老年代,基本不懂永久代

对象的finalization机制

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

垃圾回收器在回收一个对象前,会调用对象的finalize方法。finalize方法是Object中的方法

finalize()可以在子类被重写,用于对象被回收时进行资源释放(关闭文件,套接字,数据库连接)

不要主动调用finalize方法,原因:

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

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

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

基于finalize的对象的三种状态

(1)可触及的:可达性分析算法下,可以到达

(2)可复活的:可达性分析算法下,不可以到达,但是对象有可能在finalize中复活

(3)不可触及的:finalize方法被调用,对象没有复活。finalize方法在对象的声明周期中只能调用一次。

只有对象为不可触及时可以被回收

判断一个对象是否可回收,需要经历两次标记过程

(1)对象到GC Roots没有引用链,进行第一次标记。

(2)进行筛选,判断对象是否有必要执行finalize方法

(1)对象没有重写finalize方法,或者finalize方法已经执行过。则虚拟机认为不必执行,对象认为是不可触及的
(2)finalize方法重写,并且没有执行过,对象加入到F-Queue队列中,由一个虚拟机自动创建的,低优先级的finalizer线程
出发其finalize方法执行。
(3)finalize方法是对象逃离死亡的最后机会,稍后GC会对F-Queue队列中的对象进行第二次标记。如果对象在引用链上,则移出
即将回收集合。当对象再次出现不在引用链上时,对象直接成为不可及。

垃圾回收相关算法

标记阶段的算法

在垃圾回收器进行垃圾回收之前,我们需要找出堆中哪些对象是需要回收的对象。

引用计数法
  1. 介绍

    每个对象保存一个整形的引用计数器属性,用于记录对象被引用的情况。当对象被其他对象引用时,引用计数器加1,当引用失效时,引用减1
    当引用计数器的值为0时,说明对象不再被使用,可以回收。

  2. 优点

    简单,垃圾对象便于辨识,判断效率高,回收没有延迟性

  3. 缺点

    需要一个字段存储计数器,有一定的空间开销

    引用改变时需要对计数器进行修改,由一定的时间开销

    无法处理循环引用的问题。因此在Java中并没有使用此算法

  4. 补充

    引用计数法系统吞吐量较高,在标记阶段不需要STW(stop the world)

    python中选用了引用计数法

    python解决循环引用的方式:(1)手动解除 (2)使用弱应用

可达性分析算法

可达性分析算法又称为(跟搜索算法,追踪性垃圾回收)

  1. 介绍

    GC Roots:一组必须活跃的引用

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

    内存中活着的对象都会被GC Roots中的对象直接或间接连接。搜索走过的路径称为引用链。

    如果目标对象不被任何引用链相连,则为不可达,意味着不再被使用,可以被回收。

  2. 优点

    简单,高效,可以解决循环依赖的问题,Java选择了此种算法

  3. 缺点

    使用可达性分析算法来判断内存是否可回收,分析工作需要在一个能保障一致性的快照中进行。

    这是导致垃圾回收时必须Stop The World的一个重要原因

  4. GC Roots中包含的对象

    (1)栈帧中局部变量表中的对象(方法参数,局部变量)

    (2)本地方法栈中引用的对象

    (3)方法区中类静态属性引用的对象,方法区中常量引用的对象(字符串常量池中的引用)

    (4)被同步锁synchronized持有的对象

    (5)Java虚拟机内部的引用(基本数据类型对应的Class对象,一些常驻的异常对象)

    (6)反应Java虚拟机内部情况的JMXBean,JVMTI中注册的回溯、本地代码缓存

    除了这些固定的GC Roots集合以外,根据用户所选用的垃圾回收器以及当前回收的内存区域的不同,还可以有其他对象临时性的加入。

    如果只针对Java堆中的某一块区域进行垃圾回收(比如只对新时代进行回收),这个区域的对象又可能被其他区域的对象引用。这时候需要将关联的区域对象也加入到GC Roots集合中考虑,才能保证可达性分析的准确性。

清除阶段的算法

标记-清除算法(Mark-Sweep)
  1. 执行过程

    (1)从GC Roots开始遍历,标记所有被引用的对象,一般在对象的Header中记录为可达对象。

    (2)对堆内存进行从头到尾的线性遍历,如果发现某个对象为不可达,就将其回收。

    标记需要遍历一次,清除时需要遍历一次

  2. 回收是指什么

    将地址空间加入到空闲地址列表中,加入新对象时,直接进行覆盖

  3. 缺点

    效率不高;gc时需要STW;空闲内存不连续,需要维护空闲列表

复制算法(Copying)

核心思想:将空间分为两块,每次使用其中一块。在进行垃圾回收时,把存活对象复制到空闲内存中。清除使用的内存块中的所有对象。

只需要遍历一次,标记的同时进行复制。

  1. 优点

    没有标记和情况过程;简单;高效

    避免空间碎片问题

  2. 缺点

    需要两倍内存

    对于G1这种拆分为大量region的GC,复制而不是移动,意为GC需要维护region之间对象的引用关系,需要内存占用和时间开销

    如果存活对象很多,那么复制量很大。因此适用于新生代

标记-压缩算法(Mark-Compact)

背景:复制算法使用与新生代淘汰率高的区域,不适用于老年代这种存活对象占大多数的区域。

  1. 执行过程

    (1)标记所有可达对象

    (2)将存活对象压到内存一端,按顺序排放

    (3)清理边界外的空间

  2. 优点

    没有内存碎片,分配新内存时修改指针的偏移量即可。

    消除了复制算法内存减半的代价

  3. 缺点

    效率不如复制算法

    对象的移动意味着地址的改变,因此需要调整引用的地址

    移动过程中需要STW

分代收集算法

分代收集算法的基础:不同对象的声明周期不同。对于不同生命周期的对象可以采用不同的收集方式。以提高回收效率。

一般把Java分为新生代和老年代,这样就可以根据各个年代的特点使用不同的回收算法,提高垃圾回收的效率。

目前几乎所有的GC都是采用分代收集算法算法执行垃圾回收的

  1. 年轻代

    年轻代的特点:区域相对老年代较小;生命周期较短,存活率低;回收频繁

    适合复制算法,复制算法占用内存的问题可以通过设置Survivor区域的比例来缓解

  2. 老年代

    老年代特点:区域加大;对象生命周期长;存活率高;回收频率较低

    这种情况下,复制算法明显不合适。一般使用标记回收或标记压缩算法或混合使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值