JVM2 垃圾回收(GC)

本文围绕JVM垃圾回收展开,介绍了判断对象可回收的方法,如引用计数法、可达性分析算法等;阐述了标记清除、标记整理、复制、分代回收等垃圾回收算法;讲解了串行、吞吐量优先、响应时间优先、G1等垃圾回收器;最后从内存、锁竞争等领域给出垃圾回收调优策略及案例。

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

1、引用计数法

  • 当对象的引用计数变为零时,会被垃圾回收
    -

  • 弊端:循环引用时无法被垃圾回收

2、可达性分析算法(JVM采用)

  • Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
  • 原理:扫描堆中的对象,看是否能够沿着 GC Root对象为起点的引用链找到该对象,找不到,表示可以回收
  • 系统类对象、操作系统引用的Java对象、正在加锁的对象、活动线程对象都可以作为GC Root
    • 查看
      • 打开IDEA控制台
      • jps查看进程id
      • jmap -dump:format=b(二进制),live,file=1.bin 进程id
      • MAT打开bin文件查看gc_roots

3、五种引用

在这里插入图片描述
实线箭头为强引用,后四种引用对象被垃圾回收的前提为该引用对象无强引用。

  • 强引用
    • 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
  • 软引用(SoftReference)
    • 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引对象
    • 可以配合引用队列来释放软引用
    • 应用
	private static final int _4MB = 4 * 1024 * 1024;

    public static void main(String[] args) {
        List<SoftReference<byte[]>> list = new ArrayList<>();

        // 引用队列
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

        for (int i = 0; i < 5; i++) {
            // 关联了引用队列, 当软引用所关联的 byte[]被回收时,软引用自己会加入到 queue 中去
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());
        }

        // 从队列中获取无用的 软引用对象,并移除
        Reference<? extends byte[]> poll = queue.poll();
        while( poll != null) {
            list.remove(poll);
            poll = queue.poll();
        }

        System.out.println("===========================");
        for (SoftReference<byte[]> reference : list) {
            System.out.println(reference.get());
        }

    }
  • 弱引用(WeakReference)
    • 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
    • 可以配合引用队列来释放弱引用自身
    • 应用
private static final int _4MB = 4 * 1024 * 1024;

    public static void main(String[] args) {
        //  list --> WeakReference --> byte[]
        List<WeakReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB]);
            list.add(ref);
            for (WeakReference<byte[]> w : list) {
                System.out.print(w.get()+" ");
            }
            System.out.println();

        }
        System.out.println("循环结束:" + list.size());
    }
  • 虚引用(PhantomReference)

    • 必须配合引用队列使用,主要配合 ByteBuffffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存(unsafe.freeMemory)
  • 终结器引用(FinalReference)

  • 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的finalize方法,第二次 GC 时才能回收被引用对象

二、垃圾回收算法

1、标记清除

在这里插入图片描述

  • 过程: 标记没有被GC Root引用的对象,把标记的对象内存清除(将内存起始结束地址记录在空闲地址列表)
  • 优点: 速度快
  • 缺点: 容易产生内存碎片(不连续的存储空间)

2、标记整理

在这里插入图片描述

  • 过程: 标记没有被GC Root引用的对象,清除过程中将可用的对象向前移动
  • 优点: 不产生内存碎片
  • 缺点: 速度较慢

3、复制

在这里插入图片描述

  • 过程: 标记被GC Root没有引用的对象 将引用对象从FROM复制到TO 并交换两者位置
  • 优点: 不产生内存碎片
  • 缺点: 占用双倍内存

4、分代回收

在这里插入图片描述

  • 老年代和新生代GC

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

参数含义
堆初始大小-Xms
堆最大大小-Xmx 或 -XX:MaxHeapSize=size
新生代大小-Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size )
幸存区比例(动态)-XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy
幸存区比例-XX:SurvivorRatio=ratio
晋升阈值-XX:MaxTenuringThreshold=threshold
晋升详情-XX:+PrintTenuringDistribution
GC详情XX:+PrintGCDetails -verbose:gc
FullGC 前 MinorGC-XX:+ScavengeBeforeFullGC
  • 注意
    • 当存放对象所占内存大于新生代,小于老年代所占空间,大对象会直接晋升为老年代
    • 当内存溢出发生在子线程中,主线程不会结束

三、垃圾回收器

1、串行

在这里插入图片描述

  • 特点
    • 单线程
    • 堆内存较小,适合PC
  • 参数: -XX:+UseSerialGC = Serial(新生代 复制算法) + SerialOld(老年代,标记整理

2、吞吐量优先

在这里插入图片描述

  • 特点
    • 多线程
    • 堆内存较大,多核CPU,适合服务器
    • 单位时间GC的STW时间最短
  • 参数
    • -XX:+UseParallelGC (复制算法)~ -XX:+UseParallelOldGC (标记整理)
    • XX:+UseAdaptiveSizePolicy (自适应策略-动态调整)
    • -XX:GCTimeRatio=ratio (调整吞吐量:垃圾回收时间在总时间占比)
      • 公式 = 1/(1+ratio) 一般设置为19
    • -XX:MaxGCPauseMillis=ms (暂停时间 最大200ms)
    • -XX:ParallelGCThreads=n (线程数)

3、响应时间优先(CMS)

在这里插入图片描述

  • 特点
    • 多线程
    • 堆内存较大,多核CPU,适合服务器
    • 尽可能GC时单次STW时间最短
  • 参数
    • -XX:+UseConcMarkSweepGC (并发用户线程和垃圾回收线程共同运行 标记清除)~ -XX:+UseParNewGC (新生代回收 复制算法)~ SerialOld
    • -XX:CMSInitiatingOccupancyFraction=percent (CMS垃圾回收占比–预留空间给浮动垃圾)
    • XX:+CMSScavengeBeforeRemark(重新标记前对新生代垃圾回收)
  • 注意
    • CMS在内存不足时,并发失败后会使用SerialOld进行GC,导致响应时间变长
    • 和吞吐量优先区别
      • 同一GC 吞吐量:02+0.2 = 0.4 响应:0.1+0.1+0.1+0.1=0.4

4、G1(Garbage First )

  • 适用场景

    • JDK9以后为默认垃圾回收器
    • 同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停目标是 200 ms
    • 超大堆内存,会将堆内存划分为多个大小相等的Region
    • 整体上是 标记+整理 算法,两个区域之间是 复制 算法
  • 相关参数

    • -XX:+UseG1GC
    • -XX:G1HeapRegionSize=size (设置堆内存区域大小)
    • -XX:MaxGCPauseMillis=time
  • 回收阶段
    在这里插入图片描述
    新生代回收-》新生代+并发标记-》混合回收

    • 新生代回收:会STW
      • E:伊甸园区域 S:幸存区 O:老年代
        在这里插入图片描述
        在这里插入图片描述
        在这里插入图片描述
    • 新生代+并发标记
      • 在 Young GC 时会进行 GC Root 的初始标记
      • 老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW),由下面的 JVM 参数决定-XX:InitiatingHeapOccupancyPercent=percent (默认45%)
        在这里插入图片描述
    • 混合回收
      • 会对伊甸园、幸存区、老年代全面GC
      • 最终标记(Remark)会 STW
      • 拷贝存活(Evacuation)会 STW
      • 参数: -XX:MaxGCPauseMillis=ms
      • 根据最大暂停时间有选择回收老年代(红色箭头代表复制算法GC)
        在这里插入图片描述
  • Full GC

    • SerialGC
      • 新生代内存不足发生的垃圾收集 - minor gc
      • 老年代内存不足发生的垃圾收集 - full gc
    • ParallelGC
      • 新生代内存不足发生的垃圾收集 - minor gc
      • 老年代内存不足发生的垃圾收集 - full gc
    • CMS
      • 新生代内存不足发生的垃圾收集 - minor gc
      • 老年代内存不足 并发失败 - full gc
    • G1
      • 新生代内存不足发生的垃圾收集 - minor gc
      • 老年代内存不足
        • 达到阈值 并发+混合
        • 当垃圾回收速度<垃圾产生速度 - full gc
  • 新生代回收 跨代引用
    在这里插入图片描述
    跨代引用(老年代引用新生代)

    • 使用卡表技术并将老年代中引用新生代的部分标记为脏卡,新生代中使用Remembered Set记录脏卡
    • post-write barrier在每次引用发生变更时标记脏卡,该操作为异步操作。将每次操作指令放入dirty card queue
    • 使用 concurrent refinement threads 更新 Remembered Set
  • 重新标记
    在这里插入图片描述

    • 重新标记发生在并发标记阶段
    • 当白色对象引用发生改变,加入写屏障指令pre-write barrier,加入到队列satb_mark_queue变成灰色重新处理
      • 有强引用则保留,没有则回收
  • 调优

    • JDK 8u20 字符串去重
    • JDK 8u40 并发标记时类卸载
    • JDK 8u60 回收巨型对象
      在这里插入图片描述
      H为巨型对象(一个对象大于 region 的一半时,称之为巨型对象)
    • JDK 9 并发标记起始时间的调整

四、垃圾回收调优

Java位置  -XX:+PrintFlagsFinal -version | findstr "GC" 打印有关当前版本JVM的GC信息

1、调优领域

  • 内存
  • 锁竞争
  • CPU占用
  • IO

2、确定目标

  • 低延迟(互联网项目)还是高吞吐量(科学计算),选择合适的回收器
    • 低延迟:CMS、G1、ZGC
    • 高吞吐量:ParallelGC
    • Zing

3、 最快的GC是不发生GC

  • 数据是否太多
    - resultSet = statement.executeQuery(“select *from 大表 limit n”)
  • 数据是否臃肿
    • 对象图
    • 对象大小 Integer 24 int 4
  • 是否存在内存泄漏
    • Static Map map -》第三方缓存实现(redis)
    • 软、弱引用

4、新生代调优

  • 新生代特点
    • 所有的new操作的内存分配非常廉价
    • 死亡对象的回收代价是零(复制算法)
    • 大部分对象用过即死
    • Minor GC时间远远小于Full GC
  • 调优
    • 新生代内存并非越大越好(老年代空间较小-引发FullGC)
      • -Xmn:设置新生代初始和最大值
      • 理想内存:新生代能容纳所有【并发量 * (请求-响应)】的数据 较少触发Minor GC
    • 幸存区:幸存区大到能保留【当前活跃对象+需要晋升对象】
    • 晋升阈值配置得当,让长时间存活对象尽快晋升
      • -XX:MaxTenuringThreshold=threshold 最大晋升阈值
      • -XX:+PrintTenuringDistribution 幸存区详情信息打印

5、老年代调优(以CMS为例)

  • CMS 的老年代内存越大越好(避免浮动垃圾引起并发失败)
  • 先尝试不做调优,如果没有 Full GC 无需调优,否则先尝试调优新生代
  • 观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
    • -XX:CMSInitiatingOccupancyFraction=percent 老年代内存占用达到设定值CMS (一般设定为75%-80%)

6、案例

  • 案例1:Full GC 和 Minor GC频繁
    • 分析:新生代内存较低
    • 解决:增大新生代内存、幸存区内存、晋升阈值
  • 案例2:请求高峰期发生 Full GC,单次暂停时间特别长 (CMS)
    • 分析:CMS重新标记时间长,应该减少新生代数量
    • 解决:重新标记前对新生代GC
      • 参数:-XX:+CMSScavengeBeforeRemark
  • 案例3:老年代空间充裕,但发生Full GC(CMS jdk1.7)
    • 分析:永久代空间不足
    • 解决:增大永久代空间
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值