• 浅析堆与垃圾回收


      这篇文章我们主要关注这些问题::Java程序执行完后,堆中的对象什么时候被回收?如何回收?

      堆又叫做 “GC堆" 由于现在收集器基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代,比例是1:2;再细致一点新生代内部又划分为Eden区、Survivor区,比例为8:1。下图显示了堆的结构:

      对象在堆中内存的分配是有严格规定的,策略为:

    • 对象优先在新生代Eden区分配内存;
    • 大对象直接进老年代,主要是长字符串和数组这些需要大量连续内存空间的对象;
    • 长期存活的对象进入老年代。Eden区内存不够时,JVM发起一次MinorGC,对象的年龄加一,默认对象年龄到15时进入老年代;
    • 动态年龄判定。相同年龄所有对象大小的总和大于 Survivor 空间的一半,大于等于该年龄的对象进入老年代。

    新生代 GC指Minor GC,在新生代的进行垃圾回收,频繁且快。 老年代 GC(Major GC/Full GC)在老年代进行垃圾回收,通常伴随着至少一次的minor gc,速度慢。Full GC在如下几种情况下都会被触发:

    1. 老年代空间不足;
    2. 方法区空间不足;
    3. 调用System.gc(),建议JVM进行full gc;
    4. 长期存活的对象转入老年代,空间不足;
    5. 没有足够的连续空间分配给大对象;
    6. 新生代垃圾回收存活的对象太多,S1放不下,老年代担保空间不足,担保空间指的是老年代最大可用的连续空间是否大于新生代所有对象总空间。

      堆里面几乎放了所有的对象,那我们怎么知道这些对象是否还有用呢?JVM提供了两种方法来判定:

    • 引用计数法:给对象添加一个引用计数器,每次被引用,计数器值加一,引用失效,计数器值减一,当引用数为0时,表示对象不存活。引用计数法无法解决循环引用问题,周志明老师书里面有详细的例子,也是比较容易理解的。
    • 可达性分析法:以 ”GC Roots“对象为起始点,就像是树的根节点,向下搜索,搜索走过的路径称为引用链,如果一个对象到 GC Roots起始点没有引用链,则此对象不可达,是需要被回收的。GC Roots是指虚拟机栈引用的对象,本地方法栈引用的对象,方法区静态属性引用的对象,方法区常量引用的对象。

      上面提到了引用,对象的存活都和引用有关,引用类型又分为强引用,软引用,弱引用,虚引用。

    • 强引用,new出来的对象,垃圾回收器绝不会回收它;
    • 软引用,在系统将要发生OMM前会回收这些对象的内存;一些不太重要的资源,可以使用软引用包装,内存不够时来回收。使用场景可以是下面这种:
      • 软引用的使用是创建SoftReference对象,下面的代码 soft 对象 关联了byte数组。当byte对象被回收了,软引用对象还是存活的,值是null,虽然占用空间比较小,但还需要回收。可以使用引用队列。
    ReferenceQueue<byte[]> list = new ReferenceQueue<>(); 
    for (int i = 0; i < 4; i++) {
        // soft关联了引用队列,当软引用关联的byte数组被回收时,软引用对象本身会加入到队列中去,
           SoftReference<byte[]> soft = new SoftReference<>(new byte[_4MB],queue);
           list.add(soft)
      }
    // 再从队列中将无用的软引用对象移除
    Reference<? extends byte[]> poll = queue.poll(); 
    while (poll != null) {
        list.remove(poll);
        poll = queue.poll(); 
    }
    • 弱引用,垃圾收集器工作时只要发现,马上回收;
    WeakReference<byte[]> soft = new WeakReference<>(new byte[4]);
    • 虚引用,形同虚设,任何时候都可能被回收。

      实际上可达性分析法判定的不可达对象不会马上回收,对象真正被回收需要经过两次标记。第一次标记就是被判定为不可达对象,然后进行一次筛选,筛选条件是此对象是否有必要执行finalize()方法。如果对象没有重写finalize()方法或者finalize()方法已经被虚拟机调用过,finalize()方法只会被系统调用一次,这两种情况都是”没有必要执行的“。如果有必要,这个对象会被放在F-Quene队列中,由虚拟机自动建立的低优先级的Finalizer线程去执行finalize()方法。这期间GC会对F-Quene中的对象进行第二次小规模标记,如果对象依然没有被引用,那就会被回收,没有被筛选的对象不一定被回收。

     我们已经知道对象什么时候被回收了,那如何回收呢?介绍四种最常用的垃圾回收算法:

    • 标记-清除:先标记需清除的对象,统一回收效率不高,会产生大量不连续的碎片;
    • 复制算法:将内存分块,每次只使用一块,使用完后,将存活的对象复制到另一块上;
    • 标记整理:先标记存活对象,然后让所有存活对象向一端移动,直接清理端边界以外的内存;
    • 分代算法,新生代每次收集都会有大量的对象死去,选择复制算法。老年代存活率比较高,且没有额外空间进行分配担保,选择标记清除或者标记整理算法。

     垃圾收集算法是一种内存回收的思想,具体的实现是垃圾收集器。简要介绍下常用的垃圾收集器:

    • serial串行收集器:单线程,垃圾回收的时候,必须暂停其他工作。新生复制,老年标记整理,简单高效;
    • ParNew 收集器:serial的多线程版本;
    • Parallel Scavenge 收集器:复制算法的多线程收集器。注重吞吐量,cpu运行代码时间/cpu耗时总时间。新生复制,老年标记整理;
    • Serial Old 收集器:老年代版本;
    • Parallel Old 收集器:Parallel Scavenge老年代版本;
    • CMS 收集器,注重最短时间停顿。并发收集器,垃圾收集线程与用户线程(基本上)同时工作。 标记清除算法

      关于垃圾收集器更多的细节可以阅读周志明老师的书。

     

    参考资料:《深入理解Java虚拟机》第二版 周志明

         《深入拆解Java虚拟机》郑雨迪

         《JVM虚拟机底层原理分析与性能调优》程序员诸葛 

  • 相关阅读:
    C# WinForm下,隐藏主窗体,只在进程管理器中显示进程,在任务栏,状态栏都不显示窗体的方法
    C#全能数据库操作类及调用示例
    多个汇总列转换为行记录 mssql
    Oracle 10g创建数据库 用户等基本操作
    Jquery基本选择器 层次选择器 过滤选择器 表单选择器使用示例 带注释
    SQL与ORACLE的外键约束级联更新和删除
    C# 屏幕监控 自动截屏程序 主窗体隐藏,仅在进程中显示
    图文讲解VS2010程序打包操作 安装卸载
    查表法按日期生成流水号 mssql
    给DataTable添加主键 几何级提升Select筛选数据的速度
  • 原文地址:https://www.cnblogs.com/fly-bryant/p/13225163.html
Copyright © 2020-2023  润新知