JVM----②垃圾回收

垃圾回收
1. 如何判断对象可以回收
2. .垃圾回收算法
3. 分代垃圾回收
4. 垃圾回收器
5. 垃圾回收调优

1. 如何判断对象可以回收

1.1 引用计数法
给对象添加一个引用计数器,每当有一个地方引用它时,计数器加1。引用失效时,计数器减1 。当计数器为0时,对象就不可能再被使用。---------残留问题:循环引用
在这里插入图片描述

1.2 可达性分析算法
通过Gc-Root的对象为起点,通过这个节点向下搜索,搜索所走过的路径为引用链。当一个对象到Gc-Root没有任何引用链连接时,证明对象不可用。

  • 可作为Gc-Root的对象:
    • 虚拟机栈中的引用对象。
    • 本地方法栈中引用的对象。
    • 方法区类静态属性引用的对象。
    • 方法区中常量引用的对象。

可视化看哪些可以作为GC-Root对象
在这里插入图片描述
在这里插入图片描述

1.3 四种引用

  1. 强引用

    • 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收,GC Roots直接或间接引用的对象为强引用。
  2. 软引用(SoftReference)

    • 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象
    • 可以配合引用队列来释放软引用自身,当软引用引用的对象被垃圾回收后,该软引用就会进入引用队列(下图所示)
  3. 弱引用(WeakReference)

    • 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
    • 可以配合引用队列来释放弱引用自身,当弱引用引用的对象被垃圾回收后,该弱引用就会进入引用队列(下图所示)
  4. 虚引用(PhantomReference)

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

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

在这里插入图片描述

在这里插入图片描述
为什么需要引用队列:因为软,弱引用本身也占有内存,当放到引用队列后,可以变量查看它们是否被强引用引用着,再做后续处理。

虚引用
在这里插入图片描述
在这里插入图片描述
创建ByteBuffer对象时会创建一个Cleaner虚引用对象,ByteBuffer会被分配一个直接内存,并且把直接内存地址传递给虚引用对象,这样做的目的:将要ByteBuffer一旦没有了强引用,ByteBuffer自己可以被垃圾回收掉,但是还不够,因为它分配的直接内存并不能被java所管理,所以就要在ByteBuffer被回收时让虚引用对象入引用队列, 后台有 Reference Handler 线程会到引用队列找虚引用对象,调用虚引用相关方法释放直接内存;

关于ByteBuffer直接内存的分析在JVM----①内存结构直接内存有讲到。

演示软引用

//jvm 启动参数设置 -Xmx20m 堆为20M
public class Demo2_3 {
    private static final int _4MB = 4 * 1024 * 1024;//4M
    public static void main(String[] args) throws IOException {
        List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(new byte[_4MB]);
        }
    }
    测试上面会发生:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    
    如果 List<byte[]> list = new ArrayList<>(); list里面的byte数组是读取很大很大很大很大的网络资源,
    就没有必要一定要保证准确性,可以在不影响主业务的情况下能够完成就可以了,这里就可以用软引用进行改进
    
------------------use SoftReference improve--------------------------
//同样 jvm 启动参数设置 -Xmx20m 堆为20M
public static void soft() {
        // list --> SoftReference --> byte[]

        List<SoftReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());

        }
        System.out.println("循环结束:" + list.size());
        for (SoftReference<byte[]> ref : list) {
            System.out.println(ref.get());
        }
    }
}

输出:
[B@7f31245a
1
[B@6d6f6e28
2
[B@135fbaa4
3
[B@45ee12a7
4
[B@330bedb4
5
循环结束:5
null
null
null
null
[B@330bedb4  //最后list只有一个byte[]了,对应上面加的第5个元素

添加参数-Xmx20m -XX:+PrintGCDetails -verbose:gc测试:
[B@7f31245a
1
[B@6d6f6e28
2
[B@135fbaa4
3
[GC (Allocation Failure) [PSYoungGen: 1941K->504K(6144K)] 14229K->13007K(19968K), 0.0012398 secs] [Times: user=0.03 sys=0.00, real=0.00 secs] 
[B@45ee12a7
4
[GC (Allocation Failure) --[PSYoungGen: 4712K->4712K(6144K)] 17215K->17215K(19968K), 0.0036470 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 4712K->4586K(6144K)] [ParOldGen: 12503K->12465K(13824K)] 17215K->17052K(19968K), [Metaspace: 3246K->3246K(1056768K)], 0.0168497 secs] [Times: user=0.05 sys=0.00, real=0.02 secs] 
[GC (Allocation Failure) --[PSYoungGen: 4586K->4586K(6144K)] 17052K->17060K(19968K), 0.0011425 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 4586K->0K(6144K)] [ParOldGen: 12473K->649K(8704K)] 17060K->649K(14848K), [Metaspace: 3246K->3246K(1056768K)], 0.0061569 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[B@330bedb4
5
循环结束:5
null
null
null
null
[B@330bedb4
Heap
 PSYoungGen      total 6144K, used 4377K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
  eden space 5632K, 77% used [0x00000000ff980000,0x00000000ffdc6790,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 8704K, used 649K [0x00000000fec00000, 0x00000000ff480000, 0x00000000ff980000)
  object space 8704K, 7% used [0x00000000fec00000,0x00000000feca27d0,0x00000000ff480000)
 Metaspace       used 3253K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 352K, capacity 388K, committed 512K, reserved 1048576K

解决前面为null的问题,目的是将软引用本身也回收掉,结合引用队列实现

/**
 * 演示软引用, 配合引用队列
 */
public class Demo2_4 {
    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());
        }
    }
}
输出:
[B@7f31245a
1
[B@6d6f6e28
2
[B@135fbaa4
3
[B@45ee12a7
4
[B@330bedb4
5
===========================
[B@330bedb4//就只有一个了

演示弱引用

/**
 * 演示弱引用
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 */
public class Demo2_5 {
    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 < 5; i++) {// 5 10 
            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());
    }
}

输出:
[B@7f31245a 
[B@7f31245a [B@6d6f6e28 
[B@7f31245a [B@6d6f6e28 [B@135fbaa4 
[GC (Allocation Failure) [PSYoungGen: 1941K->504K(6144K)] 14229K->13031K(19968K), 0.0013037 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@7f31245a [B@6d6f6e28 [B@135fbaa4 [B@45ee12a7 
[GC (Allocation Failure) [PSYoungGen: 4712K->504K(6144K)] 17239K->13031K(19968K), 0.0142184 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
[B@7f31245a [B@6d6f6e28 [B@135fbaa4 null [B@330bedb4 
循环结束:5
Heap
 PSYoungGen      total 6144K, used 4769K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
  eden space 5632K, 75% used [0x00000000ff980000,0x00000000ffdaa518,0x00000000fff00000)
  from space 512K, 98% used [0x00000000fff80000,0x00000000ffffe030,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 13824K, used 12527K [0x00000000fec00000, 0x00000000ff980000, 0x00000000ff980000)
  object space 13824K, 90% used [0x00000000fec00000,0x00000000ff83bc70,0x00000000ff980000)
 Metaspace       used 3250K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 352K, capacity 388K, committed 512K, reserved 1048576K

当上面循环10次时测试:
在这里插入图片描述

2. 垃圾回收算法

2.1 标记清除
定义: Mark Sweep

  • 速度较快
  • 会造成内存碎片
    在这里插入图片描述
    2.2 标记整理
    定义:Mark Compact
  • 速度慢
  • 没有内存碎片
    在这里插入图片描述

2.3 复制
定义:Copy

  • 不会有内存碎片
  • 需要占用双倍内存空间
    在这里插入图片描述

3. 分代垃圾回收

在这里插入图片描述

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

3.1 相关 VM 参数
在这里插入图片描述
演示内存的分配策略

/**
 *  演示内存的分配策略
 */
public class Demo2_1 {
    private static final int _512KB = 512 * 1024;
    private static final int _1MB = 1024 * 1024;
    private static final int _6MB = 6 * 1024 * 1024;
    private static final int _7MB = 7 * 1024 * 1024;
    private static final int _8MB = 8 * 1024 * 1024;

    // 堆初始大小 堆最大大小  新生代大小
    // -Xms20M   -Xmx20M     -Xmn10M    -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -XX:-ScavengeBeforeFullGC
    public static void main(String[] args) throws InterruptedException {
//        new Thread(() -> {
            ArrayList<byte[]> list = new ArrayList<>();
            list.add(new byte[_8MB]);
            list.add(new byte[_8MB]);
//        }).start();

        System.out.println("sleep....");
        Thread.sleep(1000L);
    }
}

输出:
                    DefNew新生代:2049K回收前->679K回收后 9216K这个区的总内存
[GC (Allocation Failure) [DefNew: 2049K->679K(9216K), 0.0016110 secs][Tenured: 8192K->8870K(10240K), 0.0024582 secs] 10241K->8870K(19456K), [Metaspace: 3239K->3239K(1056768K)], 0.0041324 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [Tenured: 8870K->8851K(10240K), 0.0020503 secs] 8870K->8851K(19456K), [Metaspace: 3239K->3239K(1056768K)], 0.0020788 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

Heap
 def new generation   total 9216K, used 246K [0x00000000fec00000内存地址, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,   3% used [0x00000000fec00000, 0x00000000fec3d890, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 tenured generation   total 10240K, used 8851K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  86% used [0x00000000ff600000, 0x00000000ffea4fd0, 0x00000000ffea5000, 0x0000000100000000)
 Metaspace       used 3271K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 355K, capacity 388K, committed 512K, reserved 1048576K
  
当在mian线程创建子线程执行任务时,发生fullgc时不会导致jvm的退出

4. 垃圾回收器

1. 串行

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

2. 吞吐量优先

  • 多线程
  • 堆内存较大,多核 cpu
  • 让单位时间内,STW 的时间最短 0.2 0.2 = 0.4,垃圾回收时间占比最低,这样就称吞吐量高,每次0.2,一小时2次

3. 响应时间优先

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

4.1 串行

-XX:+UseSerialGC = Serial(新生代,复制算法)+SerialOld(老年代,标记整理)
在这里插入图片描述

4.2 吞吐量优先

-XX:+UseParallelGC(新生代,复制算法) ~ -XX:+UseParallelOldGC(老年代,标记整理) jdk默认的

-XX:+UseAdaptiveSizePolicy 采用对新生代自适应的内存大小,运行会动态的调整

-XX:GCTimeRatio=ratio 垃圾回收时间和总时间的占比 (1/1+ratio),达不到会扩容heap的大小来达到

-XX:MaxGCPauseMillis=ms stw暂停的时间

-XX:ParallelGCThreads=n 设置并行运行的gc线程数

在这里插入图片描述

4.3 响应时间优先

-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld
说明:
CMS并发的标记清除收集器,在老年代使用;通常配合的UseParNewGC使用,UseParNewGC在新生代的复制算法的垃圾收集器,它两是一对。但是当CMS发生并发失败的问题,他就会采取一个补救的措施,让老年代CMS并发收集器变为单线程的SerialOld垃圾收集器。当碎片过多会退化为SerialOld老年代的垃圾收集器

-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads
说明:
并行的垃圾收集线程数,一般和cpu核数一样;
并发的核心线程数,一般设置为并行线程数的1/4,

-XX:CMSInitiatingOccupancyFraction=percent
说明:
何时来进行CMS垃圾回收的内存占比,比如为80,表示当老年代使用率达到80%就会发生垃圾回收;这样是为了预留一些空间给那些浮动垃圾,CMS并发执行,所有会产生浮动垃圾,浮动垃圾就是在cms工作的同时用户线程工作而产生的垃圾

-XX:+CMSScavengeBeforeRemark
说明:
考虑到有可能新生代的对象引用老年代的对象,所以在重新标记之前做一个垃圾回收,+就是打开,-就是禁用;

并发:用户线程和垃圾收集线程并发执行。
并行:垃圾收集线程工作时用户线程不能工作。
在这里插入图片描述

4.4 G1
定义:Garbage First

  • 2004 论文发布
  • 2009 JDK 6u14 体验
  • 2012 JDK 7u4 官方支持
  • 2017 JDK 9 默认

适用场景

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

相关 JVM 参数
-XX:+UseG1GC
-XX:G1HeapRegionSize=size
-XX:MaxGCPauseMillis=time

1) G1 垃圾回收阶段
在这里插入图片描述
2) Young Collection
在这里插入图片描述
复制算法拷贝到幸存区:
在这里插入图片描述
工作一段时间,当幸存区比较多了,幸存区内对象移动一次该对象年龄+1,达到年龄会进入老年代:
在这里插入图片描述

3) Young Collection + CM

  • 在 Young GC 时会进行 GC Root 的初始标记
  • 老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW),由下面的 JVM 参数决定 -XX:InitiatingHeapOccupancyPercent=percent(默认45%)
    在这里插入图片描述
    4) Mixed Collection
    会对 E、S、O 进行全面垃圾回收
  • 最终标记(Remark)会 STW
  • 拷贝存活(Evacuation)会 STW
    -XX:MaxGCPauseMillis=ms 设置stw的时间
    在这里插入图片描述
    E的幸存对象会用复制算法复制到S区,另一些符合年龄的也会复制到S区,当然还有一些年龄达到阀值的会晋升到O老年代区,这些是属于新生代的回收,这里发生在混合收集阶段,还有一部分老年代的对象,经过了并发标记阶段,有些对象标记了就会复制到别的O老年代区,考虑到 XX:MaxGCPauseMillis 参数的配置,jvm会根据最大暂停时间有选择的进行某一些O老年代进行回收,选出回收价值最高的O区进行垃圾回收达到最大暂停时间的设置。

5) Full GC

  • SerialGC
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足发生的垃圾收集 - full gc
  • ParallelGC
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足发生的垃圾收集 - full gc
  • CMS
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足
  • G1
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足

6) Young Collection 跨代引用

  • 新生代回收的跨代引用(老年代引用新生代)问题

  • 卡表与RememberedSet

  • 在引用变更时通过 post-writebarrier + dirtycardqueue

  • concurrentrefinementthreads更新RememberedSet

在这里插入图片描述

5.垃圾回收调优

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

5.1 调优领域

  • 内存
  • 锁竞争
  • cpu 占用
  • io
  • GC参数

5.2 确定目标

  • 【低延迟】还是【高吞吐量】,选择合适的回收器
  • CMS,G1,ZGC
  • ParallelGC
  • Zing

5.3 最快的 GC
答案是不发生 GC
查看 FullGC 前后的内存占用,考虑下面几个问题

  • 数据是不是太多?
  • 数据表示是否太臃肿?
    • 对象图; 查数据库查需要的数据,不要一把抓
    • 对象大小 16 Integer 24 int 4 对象的瘦身,能用int不用Integer
  • 是否存在内存泄漏?
    • 软引用
    • 弱引用
    • 第三方缓存实现

5.4 新生代调优

  • 新生代的特点

    • 所有的 new 操作的内存分配非常廉价
      • TLAB thread-local allocation buffer
    • 死亡对象的回收代价是零
    • 大部分对象用过即死
    • Minor GC 的时间远远低于 Full GC
  • 越大越好吗?
    -XmnSets the initial and maximum size (in bytes) of the heap for the young generation (nursery).GC is performed in this region more often than in other regions. If the size for the younggeneration is too small, then a lot of minor garbage collections are performed. If the size is toolarge, then only full garbage collections are performed, which can take a long time to complete.Oracle recommends that you keep the size for the young generation greater than 25% and lessthan 50% of the overall heap size.

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

  • 幸存区大到能保留【当前活跃对象+需要晋升对象】

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

5.5 老年代调优

以 CMS 为例

  • CMS 的老年代内存越大越好,防止浮动垃圾多导致退化为SerialOld老年代的垃圾收集器
  • 先尝试不做调优,如果没有 Full GC 那么已经…,否则先尝试调优新生代
  • 观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
    • -XX:CMSInitiatingOccupancyFraction=percent 老年代内存占比达到就触发gc

5.6 案例

  • 案例1 Full GC 和 Minor GC频繁
  • 案例2 请求高峰期发生 Full GC,单次暂停时间特别长(CMS)
  • 案例3 老年代充裕情况下,发生 Full GC (CMS jdk1.7)
发布了138 篇原创文章 · 获赞 3 · 访问量 7249

猜你喜欢

转载自blog.csdn.net/weixin_43719015/article/details/104858985