Java虚拟机垃圾收集机制

作者: xuweizhen | 来源:发表于2019-12-17 18:58 被阅读0次

    垃圾收集机制

    在虚拟机内存模型中:

    1. 程序计数器,消耗内存可以忽略不计
    2. 虚拟机栈,在编译期可知需要分配多少内存空间,栈帧入栈分配空间,出栈回收内存。
    3. 本地方法栈,与虚拟机栈基本一致。
    4. Java堆,最大的内存区域,对象几乎在运行时才分配内存,创建频繁甚至需要同步分配,所以堆内存自动回收机制特别重要。
    5. 方法区,同样需要内存回收。

    本章了解整个垃圾收集机制

    1. 了解垃圾收集流程
    2. 重点掌握虚拟机垃圾收集算法:
      • 对象存活判定算法
      • 垃圾收集算法
    3. 虚拟机GC算法实现
    4. 垃圾收集器
    5. 对象在GC时如何分配

    对象存活判定算法

    什么时候发生GC

    当分配Java对象内存时,Java堆内存空间不够,且无法扩展时,进行一次GC

    GC处理的是Java堆中的对象,哪些对象是需要回收的呢

    这就需要对象存活判定算法,目前有两种对象存活判定算法

    1. 引用计数法
    2. 可达性分析算法

    引用计数法是什么

    对象中设置一个引用计数器,每当有一个地方引用它时,计数器值加1。引用失效时,计数器值减1。

    计数器值为0的对象就是可被回收的对象。

    引用计数法有什么优缺点

    • 优点:实现简单,判定效率高
    • 缺点:无法解决对象直接相互循环引用的问题

    什么是对象间的相互循环引用

    例如:对象A和对象B都有字段ref, 现在让A.ref=B, B.ref=A

    然后手动执行GC, 虽然这两个对象不可能再被访问,但他们之间的相互引用让引用计数器都不等于0,无法被回收。

    java虚拟机使用的是引用计数法吗

    不是,因为对象直接相互循环引用的问题,java虚拟机使用的是可达性分析算法

    什么是可达性分析算法

    通过一系列的GC Roots的对象作为起始点,从这些起始点开始向下搜索,搜索走过的路径被称为引用链

    当一个对象到GC Roots没有任何应用链相连,则证明对象是不可用的。

    也可以说,从GC Roots到对象不可达时,则对象可回收。

    一系列的GC Roots对象,GC Roots对象有哪些

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

    当GC Roots到对象不可达时,对象就一定可以被回收吗

    当GC Roots到对象不可达时,对象不一定会被回收,需要经历两次标记。

    1. 第一次GC Roots 到对象不可达时,对象被标记1次。
    2. 判断对象是否覆盖了finalize()方法
      • 对象覆盖了finalize()方法,判断虚拟机是否执行了finalize方法
        • 虚拟机没有执行finalize方法,将对象放入F-Queue队列中,待回收
          • 稍后虚拟机自动建立低优先级的Finalizer线程执行F-Queue队列中对象的finalize方法。
            • finalize方法中对象与引用链上链接建立关联,即GC Roots到对象可达
            • finalize方法中对象没有雨引用链上链接建立关联
          • 稍后GC对F-Queue队列中对象进行第二次标记
            • 与引用链建立链接的对象,移除待回收集合
            • 没有与引用链建立链接的对象,对象可回收
        • 虚拟机已经执行了finalize方法,对象可回收
      • 对象没有覆盖finalize()方法,对象可回收

    虚拟机自动建立的低优先级的Finalizer线程执行的时间很长怎么办

    Finalizer线程中对象finalize方法可能并不会等待方法执行结束,因为finalize方法可能出现死循环等异常情况,导致整个内存回收机制崩溃。

    所以只要执行了finalize方法的对象,且没有与引用链建立关联,对象就是可回收的。虽然可能方法没有结束。

    对象的finalize方法有点像C++的析构函数呀

    是的,finalize方法就是Java对C++做出的妥协。
    尽量不要使用这个方法,try-catch-finally可以做的更好。

    方法区或永久代也可能被GC吗

    方法区或永久代也会被GC, 虽然效率会很低。

    方法区或永久代会回收两部分内容:

    1. 废弃常量
    2. 无用的类

    这里的常量是指什么

    这里是指运行时常量池中常量:字面量和符号引用

    常量如何判断可回收

    1. 堆中没有对象引用该字面量或符号引用
    2. 其他地方也没有引用该字面量或符号引用

    jdk1.8移除了永生代,会有什么变化吗

    常量被移到堆中,即常量的回收与对象的回收一致了。
    所以jdk1.8只负责类回收。

    类如何判断可回收

    条件苛刻,下面3个条件同时满足才可以回收

    1. 该类所有实例被回收,即java堆中不存在该类的任何实例
    2. 加载该类的ClassLoader已经被回收
    3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

    而且是否对类进行回收,虚拟机还提供参数进行控制:-Xnoclassgc, 是否开启类回收。

    总结

    对象存活判定算法

    1. 引用计数法
    • 原理:对象中设置一个引用计数器,当有地方引用它时,计数器+1,引用失效时,计数器-1,当计数器==0时,对象已死。
    • 优点:实现简单,判定效率高
    • 缺点:无法解决对象间直接相互循环引用问题。
    1. 可达性分析算法
    • 原理:一系列的GC Roots对象作为起始点,向下搜索,走过的路径被称为引用链。当一个对象到GC Roots没有一个引用链相连,或者说GC Roots到对象不可达时,对象已死
    • 优点:解决对象间直接相互循环引用的问题
    • GC Roots对象:
      1. 虚拟机栈中引用的对象,即栈帧中本地变量表的引用对象
      2. 本地方法栈中JNI,即Native方法中对象
      3. 方法区中静态变量引用的对象
      4. 方法区中常量引用的对象

    对象两次标记判定算法

    • 原理:对象的fianlize方法可以第一次标记已死的对象重新存活。
    • 步骤:
      1. 判断对象是否覆盖finalize方法或者判断虚拟机是否已经执行了finalize方法
      2. 如果没有覆盖finalize方法或已经执行了finalize方法,则对象必死
      3. 如果对象覆盖了finalize方法且虚拟机没有执行finalize方法,将对象放到F-Queue队列中
      4. 稍后虚拟机自动建立低优先级的线程,执行F-Queue队列中对象的finalize方法,因虚拟机资源和效率,不一定会等待所有对象的finalize方法执行完成。
      5. 稍候GC对F-Queue队列中对象进行第二次标记,如果对象在finalize方法中与引用链重新建立链接,即第二次标记存活的对象,移出待回收集合。如果第二次标记还是死亡,则对象必死。

    方法区回收判定算法

    1. 常量判定
      • 常量内容:字面量和符号引用
      • 判定算法:
        • 堆中没有对象引用该字面量或符号引用
        • 其他地方没有引用该字面量或符号引用
    2. 类判定
      • 虚拟机启用类回收卸载
        • 使用参数:-Xnoclassgc,启用类回收卸载
      • 类信息同时满足3个条件判定可卸载
        • 堆中没有该类的任何实例对象
        • 该类的ClassLoader被卸载
        • 该类对应的java.lang.Class对象没有在任何地方被引用,无法通过反射机制引用到该类

    垃圾回收算法

    垃圾回收算法有哪几种

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

    什么是标记清除算法

    1. 标记:标记出所有待回收的对象。
    2. 清除:标记完成,统一回收被标记对象的内存。

    标记清除算法有什么优缺点

    • 优点:实现简单
    • 缺点:
      1. 标记和清除的效率都不高。
      2. 一次GC后会产生大量的不连续的内存碎片,当分配大对象时,可能会无法分配内存而重新发起一次GC

    什么是复制算法

    1. 将堆内存分为大小相等两块区域,每次只用其中一块
    2. 当使用的那一块内存使用完毕,触发一次GC, 将存活的对象复制到另外一块,已使用过的内存空间一次清理。
    3. 复制时只移动堆顶指针,按顺序分配内存,不会有大量内存碎片出现

    复制算法有什么优缺点

    • 优点:解决了标记清除算法的效率问题和内存碎片问题
    • 缺点:一次只能用一半的内存空间,空间消耗太大

    复制算法如何优化内存空间消耗太大的问题

    IBM的研究表明: 98%的对象朝生夕死,熬过一次GC的对象极少,所以并不需要按1:1等比例来划分内存空间。

    内存划分:

    1. Eden空间:占内存80%,数量1
    2. Survivor空间:占内存10%,数量2
      • From空间
      • To空间

    每次使用Eden空间和Survivor空间中一个,即使用90%的空间,剩余10%的空间。
    毕竟98%只是一般理论数据,10%的空间足够存放理论上2%的存活对象。

    发生GC时,将存活对象(理论上2%)复制到Survivor的另一块空闲空间,Eden空间和已使用的一块Survivor空间回收内存。

    98%毕竟是理论值,如果超过10%的对象熬过GC, 特别是大对象,那该怎么办

    98%的对象朝生夕死,根据对象生存周期的不同可以将java堆内存分为两种:新生代和老年代

    开始内存都在新生代中分配,每熬过一次GC, 对象年龄+1,当对象年龄到15岁时,移到老年代。

    很显然,新生代的对象适合用复制算法。但如果10%的空间不够,会用老年代的空间进行担保,进入老年代的空间。

    能熬过15次GC的老年代中对象,存活率肯定比较高,用复制算法的10%空间根本不够吧

    是的,老年代中对象存活率很高,不适合使用复制算法,所以使用标记整理算法。

    什么是标记整理算法

    1. 标记:对所有可回收的对象进行标记
    2. 整理:标记完成后,所有存活的对象向一端移动,然后直接清理存活对象边界之外的内存空间。

    标记整理算法有什么优缺点

    • 优点:解决了标记整理的大量内存碎片的问题,适合老年代。老年代对象存活率高,没有额外空间对它进行分配担保。
    • 缺点:不适合新生代。新生代对象存活率低,没有复制算法效率高。

    那新生代就用复制算法,老年代就用标记整理算法就好了

    是的,这就是分代算法

    1. 新生代对象存活率低,使用复制算法,只需复制少量对象就完成收集。
    2. 老年代存活率高,且没有额外空间担保,必须用标记整理算法回收空间。

    总结

    堆内存分代

    • 原因:98%的对象朝生夕死
    • 分代:
    1. 新生代
      • 存放对象:对象起始都在新生代分配,每熬过一次GC,对象年龄+1,对象到15岁,移到老年代。
      • 复制算法空间划分:使用复制算法对新生代空间划分
        • Eden空间:占用新生代80%,1个,使用的空间。
        • Survivor空间:占用新生代20%,2个,使用其中之一。
          • From空间,占用新生代10%
          • To空间,占用新生代10%
    2. 老年代

    垃圾收集算法

    1. 标记清除
      • 原理:标记所有待回收的对象,标记完成,清理所有待回收对象内存空间。
      • 优点:实现简单
      • 缺点:标记和清理效率不高,而且产生大量内存碎片。
    2. 复制算法
      • 原理:
        • 将新生代分为两部分空间,一块Eden空间(占用80%空间)和两块Survivor空间(分别占用10%空间),每次只使用Eden空间和一块Survivor空间,即使用90%空间。
        • 发生GC时,将存活的对象复制到另一块空闲的Survivor空间,Eden空间和已使用的Survivor空间一次清理。
      • 优点:适合收集新生代朝生夕死的对象,只需复制少量的存活对象完成收集,且不会产生大量内存碎片。
      • 缺点:存活对象超过10%时,需要担保进入老年代。
    3. 标记整理
      • 原理:标记所有待回收对象,标记完成,将存活对象移到一端,端边界之外的内存空间一次清理。
      • 优点:适合对象存活率高,且无法担保的老年代。也不会产生大量内存碎片。
      • 缺点:标记和整理效率不高。
    4. 分代算法
      • 原理:在新生代使用复制算法,只需复制少量对象即可完成收集。在老年代使用标记整理算法,老年代对象存活率高,且无法担保。

    想共同学习jvm的可以加我微信:1832162841,或者进QQ群:982523529

    相关文章

      网友评论

        本文标题:Java虚拟机垃圾收集机制

        本文链接:https://www.haomeiwen.com/subject/xudenctx.html