JVM--基础--14--垃圾判定算法+四种引用+垃圾回收算法

JVM–基础–14–垃圾判定算法+四种引用+垃圾回收算法


1、JVM垃圾判定算法

常见的JVM垃圾判定算法包括:引用计数算法、可达性分析算法。

1.1、引用计数算法(Reference Counting)

  1. 引用计数算法是通过判断对象的引用数量来决定对象是否可以被回收。
  2. 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是可被回收的对象。

1.2、优点

  • 简单
  • 高效

1.3、缺点

很难处理循环引用,相互引用的两个对象则无法释放。因此目前主流的Java虚拟机都摒弃掉了这种算法。

1.4、循环引用案例

对象objA和objB都有字段instance,赋值令objA.instance=objB及objB.instance=objA,除此之外,这两个对象没有任何引用,实际上这两个对象已经不可能再被访问,但是因为互相引用,导致它们的引用计数都不为0,因此引用计数算法无法通知GC收集器回收它们。

1.4.1、代码

public class ReferenceCountingGC {
    public Object instance = null;

    public static void main(String[] args) {
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;

        objA = null;
        objB = null;

        System.gc();//GC
    }
}

1.4.2、日志

[GC (System.gc()) [PSYoungGen: 3329K->744K(38400K)] 3329K->752K(125952K), 0.0341414 secs] [Times: user=0.00 sys=0.00, real=0.06 secs] 
[Full GC (System.gc()) [PSYoungGen: 744K->0K(38400K)] [ParOldGen: 8K->628K(87552K)] 752K->628K(125952K), [Metaspace: 3450K->3450K(1056768K)], 0.0060728 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 38400K, used 998K [0x00000000d5c00000, 0x00000000d8680000, 0x0000000100000000)
  eden space 33280K, 3% used [0x00000000d5c00000,0x00000000d5cf9b20,0x00000000d7c80000)
  from space 5120K, 0% used [0x00000000d7c80000,0x00000000d7c80000,0x00000000d8180000)
  to   space 5120K, 0% used [0x00000000d8180000,0x00000000d8180000,0x00000000d8680000)
 ParOldGen       total 87552K, used 628K [0x0000000081400000, 0x0000000086980000, 0x00000000d5c00000)
  object space 87552K, 0% used [0x0000000081400000,0x000000008149d2c8,0x0000000086980000)
 Metaspace       used 3469K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 381K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 0

1.4.3、分析

从运行结果看,GC日志中包含"3329K->744K",意味着虚拟机并没有因为这两个对象互相引用就不回收它们,说明虚拟机不是通过引用技术算法来判断对象是否存活的。

2、可达性分析算法(根搜索算法)

  1. 通过判断对象的引用链是否可达来决定对象是否可以被回收。
  2. 从GC Roots(每种具体实现对GC Roots有不同的定义)作为起点,向下搜索它们引用的对象,可以生成一棵引用树,树的节点视为可达对象,反之视为不可达。

2.1、可以作为GC Roots的对象包括下面几种

  1. 虚拟机栈(栈帧中的局部变量表) 的引用对象。
  2. 方法区中的类静态属性 的引用对象
  3. 方法区中的常量 的引用对象
  4. 本地方法栈中JNI(Native方法)的引用对象

2.2、真正标记对象为可回收状态至少要标记两次。

即使在可达性分析算法中不可达的对象,也并非是"facebook"的,这时候它们暂时出于"缓刑"阶段,一个对象的真正死亡至少要经历两次标记过程。

  1. 如果对象在进行中可达性分析后发现没有与 GC Roots 相连接的引用链,那他将会被第一次标记并且进行一次筛选,筛选条件是此对象是否有必要执行 finalize() 方法。当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,虚拟机将这两种情况都视为"没有必要执行"。

  2. 如果这个对象被判定为有必要执行 finalize() 方法,那么这个对象竟会放置在一个叫做 F-Queue 的队列中,并在稍后由一个由虚拟机自动建立的、低优先级的 Finalizer 线程去执行它。

    1. 这里所谓的"执行"是指虚拟机会触发这个方法,并不承诺或等待他运行结束。
    2. finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-Queue 中的对象进行第二次小规模的标记,如果对象要在 finalize() 中成功拯救自己,只要重新与引用链上的任何一个对象关联即可。

finalize() 方法只会被系统自动调用一次。

3、四种引用

3.1、强引用

就是指在程序代码之中普遍存在的,类似"Object obj = new Object()"这类的引用

3.1.1、和GC关系

只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

3.1.2、实现

Object obj = new Object();

3.2、软引用

用来描述一些还有用但并非必需的对象

3.2.1、和GC关系

对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。

3.2.2、实现

提供了SoftReference类来实现软引用

Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);

3.3、弱引用

用来描述非必需对象的,但是它的强度比软引用更弱一些

3.3.1、和GC关系

被弱引用关联的对象,只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

3.3.2、实现

提供了WeakReference类来实现弱引用。

Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);

3.4、虚引用

也叫幽灵引用或者幻影引用,它是最弱的一中引用关系。

一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。

3.4.1、和GC关系

为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

3.4.2、实现

提供给了PhantomReference类来实现虚引用。

Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);

4、JVM垃圾回收算法

常见的垃圾回收算法包括

  1. 标记-清除算法
  2. 复制算法
  3. 标记-整理算法
  4. 分代收集算法。

在介绍JVM垃圾回收算法前,先介绍一个概念:Stop-the-World

4.1、Stop-the-World

Stop-the-world意味着 JVM由于要执行GC而停止了应用程序的执行,并且这种情形会在任何一种GC算法中发生。

当Stop-the-world发生时,除了GC所需的线程以外,所有线程都处于等待状态直到GC任务完成。

事实上,GC优化很多时候就是指减少Stop-the-world发生的时间,从而使系统具有高吞吐 、低停顿的特点。

4.2、标记—清除算法(Mark-Sweep)

  1. 是最基础的算法,后续的收集算法都是基于这种思路并对其不足进行改进而得到的。
  2. 分为"标记"和"清除"两个阶段。
    1. 标记出所有需要回收的对象
    2. 标记完成后统一回收所有被标记的对象。

4.2.1、标记阶段

标记的过程就是可达性分析算法的过程,遍历所有的GC Roots对象,对从GC Roots对象可达的对象都打上一个标识,一般是在对象的header中,将其记录为可达对象

4.2.2、清除阶段

清除的过程是对堆内存进行遍历,如果发现某个对象没有被标记为可达对象(通过读取对象header信息),则将其回收。

4.2.3、不足:

  1. 标记和清除过程效率都不高
  2. 会产生大量碎片,内存碎片过多可能导致无法给大对象分配内存。

4.3、复制算法(Copying)

4.3.1、概念

将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。

4.3.2、商业虚拟机的复制算法

1. 空间划分: Eden(8),from(1),to(1)3个区域
2. 创建对象时: 对象分配在Eden区
3. 回收时:
	1. 将Eden和from中存活着的对象一次性复制到to空间上
		1. 如果from里面的分代年龄>15(默认是15),将对象分配到老年代。
		2. 如果Eden和from中存活着的对象大于10%,借用老年代的空间。
	2. 最后清理 Eden 和from,交换from和to的角色。 

现在的商业虚拟机将内存划分为一块较大的 Eden 空间和两块较小的 Survior 空间,每次使用 Eden 空间和其中一块 Survivor。
在回收时,将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和 使用过的那一块 Survivor。

HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1,保证了内存的利用率达到 90 %。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,此时需要依赖于老年代进行分配担保,也就是借用老年代的空间。

4.3.3、不足

  1. 将内存缩小为原来的一半,浪费了一半的内存空间,代价太高
    1. 如果不想浪费一半的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况。
  2. 复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。所以在老年代一般不能直接选用这种算法。

4.4、标记—整理算法(Mark-Compact)

和标记—清除算法一样,但是标记—整理算法不是把存活对象复制到另一块内存,而是把存活对象往内存的一端移动,然后直接回收边界以外的内存,因此其不会产生内存碎片。

标记—整理算法提高了内存的利用率,并且它适合在收集对象存活时间较长的老年代。

4.4.1、不足

效率不高,不仅要标记存活对象,还要整理所有存活对象的引用地址,在效率上不如复制算法。

4.5、分代收集算法(Generational Collection)

分代回收算法实际上是把复制算法和标记整理法的结合,并不是真正一个新的算法,一般分为:老年代(Old Generation)和新生代(Young Generation),老年代就是很少垃圾需要进行回收的,新生代就是有很多的内存空间需要回收,所以不同代就采用不同的回收算法,以此来达到高效的回收算法。

4.5.1、新生代

由于新生代产生很多临时对象,大量对象需要进行回收,所以采用复制算法是最高效的。

4.5.2、老年代

回收的对象很少,都是经过几次标记后都不是可回收的状态转移到老年代的,所以仅有少量对象需要回收,故采用标记清除或者标记整理算法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值