深入JVM QC对象存活算法和垃圾回收算法

1 篇文章 0 订阅

1:JVM 为什么要垃圾回收
虚拟机在加载类的时候需要创建对象实例,并且每个对象实例都需要内存空间,如果没有垃圾回收,程序在运行一段时间就会抛出内存溢出等问题,而且虚拟机创建的对象实例,也并是每一个对象都有引用的,没有被引用的垃圾也就是垃圾对象了,完全可以被清除掉

2:了解垃圾回收

  • 当项目当中遇到了内存溢出,内存泄漏问题时,项目遇到高并发瓶颈时且优化代码已经微乎其微时,优化垃圾收集器就是一个很好的解决方案

  • 程序计数器,虚拟机栈,本地方法栈3个区域随线程生灭.栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈的操作,所有这三块区域需要多考虑回收

  • java堆以及方法区则不一样,一个接口找那个的多个实现类需要的内存不一样,一个方法中的多个分支需要的内存也不一样,虚拟机在运行期才知道哪些对象会创建,这部分内存和回收都是动态的,垃圾回收器所关注的就是这部分内存垃圾

  • 堆在虚拟机运行环境中是使用最频繁的一块内存空间,生成实例对象内存也是在此空间,所要优化虚拟机的话基本是会在堆上下文章

3:对象存活判断
当知道了堆是我们优化的重点,那么问题来了虚拟机是怎么分辨堆对象内存垃圾的,这个问题问的很好,其实虚拟机有两种判断对象存活的算法,可达性分析算法,引用计数算法,这两种算法在在虚拟机里面是交替使用的,都有各自的优缺点

3.1:Java对象引用
可达性算法和计数引用算法,都是使用内存地址引用
在JDK1.2以前,Java中引用的定义很传统: 如果引用类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用。这种定义有些狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态。
在jdk1.2之后对,java对引用的概念进行了扩展,扩展为强引用,软引用,弱引用,虚引用

  • 强引用:最常见的,一个常量用等号赋值,就把这个指向了强引用,强引用GC是永远不会回收该数据的(虚拟机停止除外)
  • 软引用:要实软引需要SoftReference来实现,当堆内存不够时,软引用对象就会被QC列入回收范围,等待下次回收
  • 弱引用:弱引用和软引用类似,都是可达性对象,都会被QC回收,不同是弱引用在QC开始回收时就会被干掉,软引用则是堆内存空间不足才会回收软引用实现用WeakRefence对象实现
  • 虚引用:PhantomReference 类实现虚引用。无法通过虚引用获取一个对象的实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

3.2:引用计数算法
给每个对象添加一个引用计数器,有一个对象地方引用时,计数器+1,失去引用则计数器-1,计数器为0时表示此对象为垃圾,但是会遇到一个问题,就是AB两个对象相互引用,此时他们的计数器都为1,但是他们两个对象其实都没有被虚拟机用到,所以主流java虚拟机没有选用引用计数算法来判定对象的生死

测试代码:
运行参数:-XX:+PrintGCDetails

public Object instance = null;
private static final int _1MB = 1024 * 1024;
// 使用该变量表示在内存中占用空间,以便可以方便观察日志.
private byte[] bigSize = new byte[2 * _1MB];

public static void main(String[] args) {
    ReferenceCountingGC objA = new ReferenceCountingGC();
    ReferenceCountingGC objB = new ReferenceCountingGC();
    objA.instance = objB;
    objB.instance = objA;
    // 设置两个变量为 null,那么对象理论上来说就是垃圾.
    objA = null;
    objB = null;
    // 假设这里发生 GC,查看日志,观察两个对象是否被回收.
    System.gc();
}

运行结果
在这里插入图片描述
可以先尝试性的看一下 GC 的日志,可以发现,在年轻代,发生了 GC 之后,年轻代的空间使用为 0,的的确确表示对象被 GC 了.所以从这个案例我们可以证明,JVM 虚拟机没有使用引用计数算法

3.3:可达性算法
通过 “QC Roots” 的对象做为起点,从这些节点出发所走过的路径为引用链,当一个对象到QC Roots没有任何引用链相链的时候说明对象不可用.也就是说QC roots对象不可达的对象,就是可以回收的垃圾对象
在这里插入图片描述
可作为QC Roots的对象:

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

3.4:HotSpot(虚拟机)的可达性分析
HotSpot 虚拟机在实现上面的算法的时候,必须要经过更严格的考量,才能保证虚拟机高效运行.比如,在上面的可达性分析中,就存
在执行效率的问题:

  1. 从 GC Roots 节点找引用链,可是现在很多应用的引用比较复杂,比如方法区就有数百兆,如果要逐个检查这里面的引用,必然消耗
    很多的时间.
  2. 为了保证整个分析期间整个执行系统被冻结,而不产生新的引用,会导致 java 执行线程停顿(stop the world).
    为了解决上面的两个问题:
    • 枚举根节点,使用一组 OopMap 的数据结构来存放对象引用,这个数据结构在类加载完成的时候,就已经计算出来了,GC 在扫描
      的时候就可以得知这些信息,从而降低 GC Roots 时间以及减少停顿时间.
    • .OopMap 中的引用关系可能会变化.或者 OopMap 的指令太多,反而需要更多的空间.此时解决方案是,OopMap 会根据虚拟机
      选定的安全点(safepoint,可以简单理解为执行到哪一行),在这个安全点内去生成指令的 OopMap.在 GC 的时候,驱使所有的线程都"跑
      "到最近的安全点,STW 才发生,应用才停顿.
    • .对于挂起的线程来说,比如处于 sleep 或者 blocked 状态的,是不能"跑"到安全点的,那么此时解决方案就是,增大安全域(SafeRegion).如果线程已经达到安全域,做一个标记,GC 就不需要管这些线程.

3.5:对象的自我拯救
在可达性算法中不可达的对象,也并不一个定是垃圾对象,其实在离他们真正死亡还有两次的标记过程

  1. 如果对象在进行中可达性分析后发现没有与 GC Roots 相连接的引用链,那他将会被第一次标记并且进行一次筛选,筛选条件
    是此对象是否有必要执行 finalize() 方法。当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,虚拟机将这
    两种情况都视为“没有必要执行”,这种情况就活不了。
  2. 如果这个对象被判定为有必要执行 finalize() 方法,那么这个对象竟会放置在一个叫做 F-Queue 的队列中,并在稍后由一个
    由虚拟机自动建立的、低优先级的 Finalizer 线程去执行它。这里所谓的“执行”是指虚拟机会出发这个方法,并不承诺或等待他运
    行结束。finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-Queue 中的对象进行第二次小规模的标记,如果对
    象要在 finalize() 中成功拯救自己 —— 只要重新与引用链上的任何一个对象简历关联即可。
    finalize() 方法只会被系统自动调用一次

4:垃圾回收算法
知道了通过可达性算法和引用计数算法知道回收那些垃圾后,我们就得了解垃圾回收算了的回收算法了,现在收集器主要是通过 标记-清除算法,标记-复制算法,整理算法,,来清除垃圾

4.1:标记-清除算法
"Mark-Sweep"算法,中文名标记清除算法,和他名字一样,分为两个步骤清除垃圾,先标记后清除,先对垃圾对象进行标记,然后在统一清除垃圾对象,标记-清除算法也是最基础的垃圾回收算法,后续的算法都是在此基础衍生而来
在这里插入图片描述
标记是属于最基础的算法,此算法没有优点,缺点有两个 ,一个效率问题,标记和清除效率不高;另一个是内存空间问题,使用标记清除算法,会导致内存中产生大量的空间碎片,空间碎片在内存中太多,导致分配存储的大的对象内存变少或者无法找到连续的空间,从而提前触发QG回收
此算法还会暂停用户线程

4.2:标记-整理算法

标记-整理(Mark-Compact)算法,标记过程和"标记-清除"算法一样,但后续步骤不一样,整理是将所有的垃圾移动到一端的边缘,然后直接清除垃圾的一端的边缘
在这里插入图片描述
标记-整理优化了标记-清除算法所带来的大量空间碎片,始内存区空间已连续的方式清除垃圾

4.3:复制算法
“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
在这里插入图片描述
这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,持续复制长生存期的对象则导致效率降低。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值