• JVM垃圾回收机制


    一、如何判断对象可以可以被回收

    1.1 引用计数法

     定义:只要一个对象被变量所引用,则该对象计数就+1,若被引用了两次,则它的引用计数就变为2,如果某一个变量不再引用它了,则它的引用计数就减一,当该对象的引用变为0的时候就表示没有变量引用它了,该对象就可以被当作垃圾回收了。

     弊端:当两个对象循环引用时候,但是又没有被别的变量引用,并且这两个对象不在有实用价值,这时,垃圾回收并不能够回收这两个对象,可能导致內存溢出。

    1.2 可达性分析算法

     根对象:那些肯定不能被当成垃圾回收的对象

     定义:在垃圾回收之前,先对堆中的所有对象进行一次扫描,判断每一个对象是否被根对象直接或者间接的引用,如果是,则不能被回收,反之则可以被当成垃圾回收

     Java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象

     扫描堆中的对象,看是否能够沿着GC Root对象(一系列对象)为起点的引用链找到该对象,找不到,表示可以回收

     哪些对象可以作为GC Root ?

      虚拟机栈(栈帧中的本地变量表)中引用的对象。
      方法区中类静态属性引用的对象。.
      方法区中常量引用的对象。
      本地方法栈中JNI(即-般说的Native方法)引用的对象。

    1.3 四种引用

     1.强引用
      只有所有GC Roots对象都不通过[强引用]引用该对象,该对象才能被垃圾回收
     2.软引用(SoftReference)
      仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象
      可以配合引用队列来释放软引用自身
     3.弱引用(WeakReference)
      仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
      可以配合引用队列来释放弱引用自身
     4.虚引用(PhantomReference)
      必须配合引用队列使用,主要配合ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法释放直接内存
     5.终结器引用(FinalReference)
      无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收), 再由Finalizer线程通过终结器引用找到被引用对象并调用它的finalize方法,第二次GC时
      才能回收被引用对象

    二、垃圾回收算法

    2.1 标记清除

      特点:速度较快、会造成內存碎片

    2.2 标记整理

      特点:速度慢、没有內存碎片

    2.3 复制

      特点:没有內存碎片、需要占用双倍内存空间

    三、分代垃圾回收

    3.1 分代垃圾回收机制

      对象首先分配在伊甸园区域
      新生代空间不足时,触发minor gc,伊甸园和from存活的对象使用copy复制到to中,存活的对象年龄加1并且交换from to
      minor gc会引发stop the world, 暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
      当对象寿命超过阈值时,会晋升至老年代,最大寿命是15 (4bit)
      当老年代空间不足,会先尝试触发minor gc,如果之后空间仍不足,那么触发full gc, stw的时间更长

    3.2 相关VM参数

    四、垃圾回收器

    4.1串行 

      单线程
      堆内存较小,适合个人电脑

    4.2吞吐量优先

      多线程
      堆内存较大,多核cpu
      让单位时间内,STW的时间最短0.2 0.2=0.4

    4.3响应时间优先

      多线程
      堆内存较大,多核cpu
      尽可能让单次STW的时间最短0.1 0.10.1 0.10.1=0.5

    4.4G1

    适用场景
      同时注重吞吐量(Throughput) 和低延迟(Low latency) ,默认的暂停目标是200 ms
      超大堆内存,会将堆划分为多个大小相等的Region
      整体上是标记+整理算法,两个区域之间是复制算法
    相关JVM参数
      -XX:UseG1GC
      -XX:G1HeapRegionSize=size
      -XX:MaxGCPauseMillis=time

    G1垃圾回收阶段

      1)Young Collection

      2)Young Collection + CM

      在Young GC时会进行GC Root的初始标记
      老年代占用堆空间比例达到阈值时,进行并发标记(不会STW),由下面的JVM参数决定
      -XX:InitiatingHeapOccupancyPercent=percent (默认45%)

      3)Mixed Collection 

       会对E、S、O进行全面垃圾回收
      最终标记(Remark) 会STW
      拷贝存活(Evacuation) 会STW
      - XX:MaxGCPauseMillis=ms

    FullGC 

    ■SerialGC
      新生代内存不足发生的垃圾收集- minor gc
      老年代内存不足发生的垃圾收集- full gc
    ■ParallelGC
      新生代内存不足发生的垃圾收集- minor gc
      老年代内存不足发生的垃圾收集- full gc
    ■CMS
      新生代内存不足发生的垃圾收集- minor gc
      老年代内存不足:并发失败以后,才叫Full GC,否则不会触发Full GC
    ■G1
      新生代内存不足发生的垃圾收集- minor gc
      老年代内存不足:当老年代內存跟堆內存占比达到45%以上,会触发并发标记的阶段,以及后续混合收集的阶段,如果垃圾回收的速度比新产生的垃圾的速度要快,来的及打扫,这是还不叫Full GC,还是并发垃圾回收的阶段(也会有暂停,但是时间很短), 当垃圾回收的速度跟不上垃圾产生的速度,并发收集就会失败,转化为Full GC(并发进行),stw时间也会更长。

    Young Collection 跨代引用

      卡表与Remembered Set
      在引用变更时通过post-write barrier + dirty card queue
      concurrent refinement threads更新Remembered Set

    Remark 

    JDK 8u20 字符串去重 

      优点:节省大量内存
      缺点:略微多占用了cpu时间,新生代回收时间略微增加
      -XX: +UseStringDeduplication   默认开启

      将所有新分配的字符串放入一个队列
      当新生代回收时, G1并发检查是否有字符串重复
      如果它们值一样,让它们引用同-一个char[]
      注意,与String. intern()不- -样
      String. intern()关注的是字符串对象
      而字符串去重关注的是char[]
      在JVM内部,使用了不同的字符串表

    JDK 8u40 并发标记类卸载 

      所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸载它
      所加载的所有类
      -XX:+ClassUnloadingWi thConcurrentMark默认启用

    JDK 8u60 回收巨型对象 

      一个对象大于region的一半时,称之为巨型对象
      G1不会对巨型对象进行拷贝
      回收时被优先考虑
      G1会跟踪老年代所有incoming引用,这样老年代incoming引用为0的巨型对象就可以在新生代垃圾回收时处理掉

    JDK 9  并发标记起始时间的调整 

      并发标记必须在堆空间占满前完成,否则退化为FullGC
      JDK9之前需要使用-XX:InitiatingHeap0ccupancyPercent
      JDK 9可以动态调整
        -XX: Initiat ingHeapOccupancyPercent用来设置初始值
        进行数据采样并动态调整
        总会添加一个安全的空档空间

    JDK 9 更高效的回收 

      250+增强

      180+bug修复

    五、垃圾回收调优 

      预备知识
         掌握GC相关的VM参数,会基本的空间调整
         掌握相关工具
         明白一点:调优跟应用、环境有关,没有放之四海而皆准的法则

    5.1 调优领域 

      ■内存
      ■锁竞争
      ■cpu占用
      ■io

    5.2 确定目标 

      [低延迟]还是[高吞吐量],选择合适的回收器
      CMS,G1(JDK9,在更大的內存下工作的比CMS要好),ZGC   (低延迟)
      ParallelGC   (高吞吐量)

      Zing(stw 0停顿、可管理超大的內存)

           互联网项目主要是针对 低延迟

    5.3 最快的GC是不发生GC 

      查看FullGC前后的内存占用,考虑下面几个问题
      数据是不是太多?
                 resultSet = statement.executeQuery("select * from大表limit n")
      数据表示是否太臃肿?
           对象图
           对象大小16 Integer 24 int 4
      是否存在内存泄漏?
           static Map map =
         软 
           弱
           第三方缓存实现

    5.4 新生代调优  

    新生代的特点 

      所有的new操作的内存分配非常廉价
          TLABlthread-local allocation buffer
      死亡对象的回收代价是零
      大部分对象用过即死
      Minor GC的时间远远低于Full GC

      新生代能容纳所有[并发量* (请求-响应)]的数据

      幸存区大到能保留[当前活跃对象+需要晋升对象]

      升阈值配置得当,让长时间存活对象尽快晋升
      -XX:MaxTenuringThreshold=threshold
      -XX:+PrintTenuringDistribution

      

    5.5 老年代调优 

        以CMS为例

      ■CMS的老年代内存越大越好
      ■先尝试不做调优,如果没有Full GC那么已经...否则先尝试调优新生代
      ■观察发生Full GC时老年代内存占用,将老年代内存预设调大1/4~ 1/3
      ■-XX: CMSInitiatingOccupancyFraction=percent

    5.6 调优案例  

    案例1  Full GC和Minor GC频繁  

     分析:GC频繁说明空间紧张,究竟是哪一空间紧张呢,如果是新生代,当业务高峰期来了,大量的对象被创建,新生代的空间很快就满了,幸存区的空间紧张,那么它的最大晋升阈值就会降低,导致很多本来生存周期很短的对象,也会晋升到老年代去,这样情况就进一步恶化,这样就导致老年代的Full GC频繁发生。

     解决:通过检测工具去观察堆空间的大小,发现新生代的內存设置的太小了,先试着增大新生代的內存,新生代的內存充裕了之后,新生代的垃圾回收就变得不那么频繁了,同时增大了幸存区的空间,以及晋升阈值,这样就能够使得生命周期较短的对象尽可能的留在新生代里,这样就可以让老年代的FullGC也不那么频繁了。

    案例2  请求高峰期发生Full GC,单次暂停时间特别长(CMS)

     分析:已确定垃圾回收器是CMS,先去查看GC日志,看看CMS哪个阶段耗时比较长,CMS在重新标记的时候要扫描整个堆内存,如果业务高峰期的时候,新生代的对象较多标记时间会变的很长。

     解决:在重新标记之前对新生代先做一次垃圾回收,减少新生代对象的数量,这样就可以减少在重新标记时所耗费的时间。    - XX:+CMSScavengeBeforeRemark

    案例3  老年代充裕情况下发生Full GC(JDK1.7  CMS)

     分析:之前介绍过CMS可能由于空间不足,导致并发失败,或者是空间碎片比较多,会产生Full GC;若日志排查后,在GC日志里没有并发失败,碎片过多的错入提示,说明老年代的空间是充裕的,不是老年代空间不足产生的Full GC。从案例中可知项目所用的JDK为1.7,JDK1.8是元空间作为方法区的实现,JDK1.7及以前是永久代作为方法区的实现,JDK1.7以前的永久代空间不足也会导致Full GC,JDK1.8以后,元空间不再由JAVA控制,元空间的默认情况下他的內存空间是使用了操作系统的內存空间,空间的容量一般是比较充裕的。而JDK1.7以前永久代的空间如果设小了,就会导致触发整个堆的Full GC。

     解决:增大永久代的最大值和初始值。

     

  • 相关阅读:
    「Docker」命令使用大全,全集一览
    6. 线程调度 与 重试机制
    RORγ 反向激动剂-XY101 小分子化合物
    【算法刷题】—7.13哈希表的应用
    ASP.NET Core Web API Action方法参数
    winlicense官方版是一款功能专业强大的编程软件
    会话与终端
    Golang编写客户端SDK,并开源发布包到GitHub,供其他项目import使用
    LQ0209 颠倒的价牌【枚举+进制】
    Comparable是排序接口 Comparator是比较器接口
  • 原文地址:https://www.cnblogs.com/yclblogs/p/15925418.html