zoukankan      html  css  js  c++  java
  • JVM之GC(二)

    昨天总结了GC之前要做的事情,今天介绍一下主流的GC算法。

    先介绍一下几个名词:

    Stop The World(STW):JVM进行GC的时候总不能一边清理垃圾一边制造垃圾把,那么垃圾鉴定的准确性根本无法得到保证,所以需要将服务全部停掉那么一瞬间;

    Yong GC、Minor GC:年轻代区域的GC,所有的年轻代GC都会触发STW;

    Old GC、Major GC:老年代区域的GC,只有串行部分会触发STW;

    Full GC:主要针对老年代区域而言的串行GC,会触发STW。

    首先说一下年龄分代收集算法,根据对象的存活周期,将堆区分为年轻代(新生代)和老年代。顾名思义,刚生成的对象放入年轻代,勤用勤收集,对象成功经历几轮GC后仍存活下来,便将它移入到老年代中去,如果老年代也满了,那么会触发Full GC。如果你是JVM设计者,你会怎么分配年轻代和老年代的比重呢?老年代收集效率差,存活率高,轻易不要移动这里的对象,如果老年代空间不充足,便会频繁触发Full GC,得不偿失;如果老年代分配过多也不好,对象全都跑到老年代了,长时间不触发清理操作会造成过于碎片化,严重导致空有内存却无法分配的现象。因此堆区应该为老年代和年轻代分配合适的比例,一般为4:1(调优参数:-XX:NewRatio=4)

    实际上大多数的JVM都是以年龄分代算法为基础,将堆区划分成独立的两个区域,然后再结合其他的GC算法分开进行GC,从而更高效。

    最先提出来的是标记清除法:先将堆区的需要回收的对象标记下来,然后统一回收。它会产生两个问题,首先是效率低下,标记和清理过程的效率都是很低的;然后是碎片问题,剩余的存活的对象分散各处,造成堆区碎片化,不方便为大对象分配内存。

    针对上面两个问题,提出了复制算法,把堆区分为一个eden区域和两个小的survivor区域,当需要申请内存时,JVM会在较大的eden区域为其分配空间,当eden区域无法为新对象提供内存时,JVM将其中一个survivor空间和eden空间中存活的对象移到另一个survivor中,然后清空eden和之前使用过的survivor,按此循环,也就是说,总有一个survivor区域是留空的,用来存放其余二者存活的对象。

    将上面两个方法对比来看,复制算法更适合用来处理存活率低的内存区域,也就意味着复制量小,更高效,显而易见,它是一个年轻代算法;相对而言,如果用标记清除法处理年轻代,使这片空间过于碎片化,当eden为较大对象分配空间时便会很乏力(实际上大对象或大数组是直接在老年代分配的),而且年轻代应该像他的名字一样高效、快速GC,该算法的标记和清除效率并不高,因此标记清除法更应该拿来处理老年代。

    针对标记清除法的碎片化问题以及老年代的特点,有人又提出了一个改进措施,标记整理法,它的思想是:让存活的对象向一端移动,然后从边界处将另一侧所有空间直接清理掉。


    前面提到了标记,内存中这么多引用变量,JVM如何寻找它是对象引用类型呢?其实,JVM使用了一组OopMap的数据结构来存放引用类型,在JIT编译和类加载的时候记录下来那些位置是引用,这样GC的时候,JVM就可以快速且准确的完成GC Roots的枚举了。

    到这里,又引出了另外一个问题,变量、对象间的引用关系是一直在变化的,JVM不可能监听每一条指令,代价太大。所以引出了安全点这个概念,如果在安全点位置上我们对OopMap进行校准的话,那么JVM是可以在这个位置进行GC的,此时STW之后的引用类型是准确的。那么什么位置可以作为安全点呢?1、所有指令的末尾均可添加 2、方法调用、循环回跳之前等可添加; 它的选定是依据“程序在此处是否会长时间执行”为标准的。

    对于多线程的程序,STW的时候是需要全部暂停的,但我们并不能保证多个线程正在执行的指令处是否有安全点,上一段已经说明只有安全点处才可以安全准确的GC。这里有两种解决方案:

    1、抢先式中断:JVM觉得可以GC了,那就先STW,然后检查所有线程是否在安全点上,不在的话就让它继续执行;很少JVM会采用这种方式

    2、主动式中断:在安全点上加一个轮询的标志,在需要GC时,将该标志设为中断值,已经在安全点上的线程便会轮询等待,未在安全点上的线程读取不到这个标志会继续执行

    如果一个线程没有分配到时间片,线程出于sleep或blocked状态如何处理呢?JVM不可能等到它分配时间片之后在GC把。因此提出了安全区域这个概念,只有这段指令不会导致引用变化的片段才可以作为安全区域使用,在GC时JVM会忽视掉处于安全区域的线程,在这个区域内的任何地方开始GC都是安全的。当线程执行到安全区域的代码时,首先标识自己进入了安全区域。如果它要走出安全区域,需要先检查JVM是否处于GC状态且是否已经完成了根节点枚举,如果满足了根节点枚举或不在GC状态,它就可以走出来继续执行代码,否则他必须得等待知道收到信号为止。

  • 相关阅读:
    ubuntu下android开发工作环境搭建
    ADB命令行控制界面开关
    chromium os系统编译与环境搭建
    完整代理的简单实现
    OC协议、代理的简单使用
    OC字典的使用
    OC数组的简单使用、NSArray
    OC中NSString的使用、字符串的使用
    OC内存管理、非ARC机制、MRR机制
    OC中重写set和get方法、懒加载
  • 原文地址:https://www.cnblogs.com/ZoHy/p/11220812.html
Copyright © 2011-2022 走看看