×

苦心钻研jvm垃圾回收

我的笔记 我的笔记 发表于2021-08-19 15:56:11 浏览3291 评论2

2人参与发表评论

垃圾回收分两步:
1.判断对象是否死亡
2.垃圾回收算法进行回收


一、判断是否死亡


       (1)引用计数器法:

    ①每当有一个对象引用是,计数器加一,当计数器为0是对象死亡

    ②缺点:无法解决循环引用的问题,假设A引用B,B引用A,那么这两个对象将不会被回收,造成内存泄漏

  (2)可达性算法分析

    ①通过一系列可作为GC Roots的节点为起始点,从这些节点开始往下搜索,所走过的路径称为引用链。

    ②当一个对象到GC Roots节点没有引用链时,说明对象不可用

    ③可作为GC Roots节点的对象

      虚拟机栈中引用的对象

      本地方法栈中引用的对象

      方法区中静态变量引用的对象

      方法区中常量引用的对象

二、回收算法整理

       (1)标记-清除:即直接将标记为死亡的对象清除,缺点是会产生垃圾碎片

  (2)标记-整理:即将可用的对象同意向一端移动,将边界外的对象清除

  (3)复制算法:即将堆分为了Eden,SurvivorFrom,SurvivorTo空间

    ①每次在Eden空间上分配对象

    ②SurvivorFrom空间为上次垃圾回收是还存活的对象

    ③SurvivorTo空间为本次垃圾回收是生存的对象存放的位置

    ④本次垃圾回收结束后交换SurvivorFrom与SurvivorTo

    ⑤复制算法需要担保空间,当有一个大的对象要分配,而Eden空间又不足时会直接分配到老年代

    ⑥在对象生存率较高时会进行大量的复制操作,降低效率

  (4)分代回收算法:根据新生代与老年代对象的特点而使用不同的垃圾会回收算法

    ①新生代:对象生存周期较短,只有少量的生存对象,适合使用复制算法

    ②老年代:对象生存周期较长,只又少量需要回收的对象,且无担保空间,所以使用标记-整理算法或者是标记-清除算法


三、经典收集器

    

        1.Serial收集器(新生代)

          (1)单线程收集器

          (2)采用复制算法,用于新生代垃圾回收

          (3)垃圾回收期间需要STW(Stop The World),STW表示垃圾回收线程不与用户线程并发执行

        2.Serial Old收集器(老年代)

          (1)与Serial相似

          (2)采用标记整理算法,用于老年代的立即回收

        3.ParNew收集器(新生代)

          (1)是Serial的多线程版本

          (2)除此之外与Serial收集相似

        4.Parallel Scavenge收集器(新生代)

          (1)基本功能与ParNew收集器相似

          (2)区别在于该收集器是要达到一个可控制的吞吐量(吞吐量=运行用户代码的时间/(运行用户代码的时间)+(垃圾回收的时间))

          (3)可以高效的利用cpu时间

          (4)提供了参数可以精确的控制吞吐量,分别是控制最大垃圾回收停顿时间,也可以直接设置吞吐量大小

        5.Parallel Old收集器(老年代)

  (1)Parallel Scavenge收集器的老年代版本,采用标记整理算法

        6.CMS收集器(老年代)

          (1)采用标记清除算法,用户老年代的垃圾回收

          (2)主要关注的是尽可能的缩短垃圾回收时用户线程的停顿时间

          (3)主要有一下几个步骤:

            ①初始标记:简单标记一下GC ROOTS能直接关联到的节点,此阶段需要STW

            ②并发标记:进行GC Roots Tracing的过程,此阶段与用户线程并发执行

            ③重新标记:对并发标记时用于线程产生的新的节点进行标记,此阶段需要STW, 但是此阶段为多线程并行的(多个垃圾回收线程同时进行)

            ④并发清除:使用标记清楚算法对对象进行回收,此阶段与同户线程同时进行

          (4)缺点:

            ①无法清除浮动垃圾,由于最后一个阶段并发清除是与用户线程同时进行的,所以用户线程可能会产生新的可会收的对象

            ②可能会产生垃圾碎片,由于该回收器采用的是标记清除算法

        7.G1收集器

          (1)G1(Garbage-First)

          (2)G1收集器作用于整个JVM堆

          (3)G1收集器将整个堆分成了大小相同的独立区域(Region)

          (4)在后台会维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region

        

四、触发回收,及其回收过程        

            通常我们所说的gc主要是针对java heap这块区域的。下面来了解一下heap区。

        

从图中我们可以看出jvm heap区域是分代的,分为年轻代,老年代和持久代。 JVM的堆区对象分配的一般规则:

1. 对象优先在Eden区分配

2. 大对象直接进入老年代(-XX:PretenureSizeThreshold=3145728 这个参数来定义多大的对象直接进入老年代)

3. 长期存活的对象将进入老年代(在JDK8中测试,-XX:MaxTenuringThreshold=1的阀值设定根本没用)

4. 动态对象年龄判定(虚拟机并不会永远地要求对象的年龄都必须达到MaxTenuringThreshold才能晋升老年代,如果Survivor空间中相同年龄的所有对象的大小总和大于Survivor的一半,年龄大于或等于该年龄的对象就可以直接进入老年代)

5. 空间分配担保

6. 只要老年代的连续空间大于(新生代所有对象的总大小或者历次晋升的平均大小)就会进行minor GC,否则会进行full GC


gc的触发条件

充分了解了jvm的内存结构之后,下面我们就来说说什么情况下会触发gc。触发full gc的情况主要有这几种:

(1)System.gc()方法的调用。此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+ DisableExplicitGC来禁止RMI(Java远程方法调用)调用System.gc。

(2)旧生代空间不足。旧生代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出错误:java.lang.OutOfMemoryError: Java heap space 。为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。

(3)Permanet Generation空间满了。Permanet Generation中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出错误信息:java.lang.OutOfMemoryError: PermGen space 。为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。

(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存。如果发现统计数据说之前Minor GC的平均晋升大小比目前old gen剩余的空间大,则不会触发Minor GC而是转为触发full GC。

(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小


我的笔记博客版权我的笔记博客版权