【JVM-3】垃圾回收

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

1. 引用计数法

(参考视频:黑马)
只要一个对象被其他变量所引用,那么这个对象计数+1;
如果某一个变量不再引用它了,计数-1
当对象的引用计数变为0,那么就可以被当做垃圾回收。

存在的问题:循环引用
在这里插入图片描述
这两个对象的引用计数不能归零(其他对象都无法引用它们了) 所以不能被当做垃圾回收掉(内存泄露)

2. 可达性分析算法

  • Java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
  • 扫描堆中的对象,看是否能够沿着GC Root对象 为起点的引用链找到该对象,找不到,表示可以回收
  • 哪些对象可以作为GC Root? 工具:Memory Analyzer(MAT) 帮助分析堆,防止内存泄露之类的…

要把堆内存dump下来:

 jmap -dump:format=b,live,file=1.bin 5580

然后将文件 1.bin放进工具

3. 四种引用

在这里插入图片描述

  1. 强引用

  2. 软引用 softReference
    没有其他强引用指向,回收完后还是内存不足,那么就回收软引用所指对象
    (软引用本身也要占用一定内存空间) 如果要清理软引用本身,那么需要配合使用引用队列

   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());
        }

    }
  1. 弱引用 WeakReference
    只要发生了垃圾回收,就会把弱引用的对象回收
    软、弱引用被回收,可以进入引用队列

  2. 虚引用
    虚引用和终结器引用必须配合引用队列来使用。
    回收后进入引用队列。

  3. 终结器引用
    (类似析构函数 当对象被回收后 终结器引用调用finalize()方法善后)

二、 垃圾回收算法

1. 标记清除

扫描堆对象的过程中,如果发现对象确实被引用了,则保留,如果没有被GC root直接或间接引用,则要回收。
两个阶段: 先标记,再清除(释放空间)
注意并不是将内存中的字节清零,而是记录这些块的起始结束地址,然后放入空闲链表里。当有新对象时候,就去空闲链表里找,有没有足够的空间容纳新对象,如果有,就进行分配。
在这里插入图片描述
优点
速度快
缺点
容易造成内存碎片(不整理内存啦 大对象放不进去)

2. 标记整理

两个阶段: 标记,整理
整理:避免内存碎片问题,将对象向前移动(紧凑
缺点:速度慢 对象要向前移动
在这里插入图片描述

3. 复制

标记,然后将from中存活的对象复制到to中。然后将from全部清空,并且交换from和to
双倍内存空间;无碎片
在这里插入图片描述

三、 分代垃圾回收

实际的垃圾回收,会结合前面的三种算法。具体为分代垃圾回收机制。

在这里插入图片描述
需要长时间使用的对象放在老年代,对于用完了就可以丢弃的对象放到新生代。这样可以根据不同生命周期的特点,采用不同的垃圾回收策略。

  • 对象首先分配在伊甸园区域
  • 新生代空间不足,触发minor gc,伊甸园和from存活的对象使用copy复制到to中,存活的对象年龄+1并且交换from和to
  • minor gc会引发stop the world(停止其他用户线程,只保留垃圾回收线程,因为发送对象复制…地址都移动了) 垃圾回收结束,用户线程才恢复运行
  • 对象寿命超过阈值时,会晋升到老年代,最大寿命是15(存寿命的是4bit 对象头中四位)
  • 当老年代空间不足,会先进行minor gc看能不能腾出一点空间。发现空间仍旧不足,就触发一次full gc,STW(stop the world)的时间更长
  • 如果还是不足,out of memory

相关VM参数

在这里插入图片描述
VM options:-Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -XX:-ScavengeBeforeFullGC
可以看到堆内存详情:
幸存区的一个1024k是不计入总数的。
在这里插入图片描述

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]);//新生代根本不够用 如果老年代足够,那么直接放入老年代
        }).start();

        System.out.println("sleep....");//内存溢出了还能运行。。。一个线程内的内存溢出不会导致主线程停止运行
        Thread.sleep(1000L);
    }
}

GC:新生代gc
Full GC:老年代
常量池在方法区中

  • 新生代内存紧张,即使没有到阈值,也可以晋升到老年代
  • 新生代根本不够用 如果老年代足够,那么直接放入老年代
    在这里插入图片描述
  • 新生代和老年代都放不下对象–内存溢出

在这里插入图片描述

四、垃圾回收器

1. 串行的垃圾回收器

单线程垃圾回收
适合:堆内存较小,适合个人电脑

-XX:+UseSerialGC = Serial + SerialOld 打开串行的垃圾回收器
复制算法 标记整理算法(老年代)
在这里插入图片描述
此图既适合新生代的gc,也适合老年代的gc(这俩区别是使用的回收算法不同)

2. 吞吐量优先的垃圾回收器

  • 多线程
  • 堆内存较大,适用于多核cpu(单核的话,切换成本就非常高)
  • 让单位时间内,STW的时间最短 0.2 0.2 总时间上会更优。
    (吞吐量:垃圾回收时间占程序运行时间的占比)

-XX:+UseParallelGC ~ -XX:+UseParallelOldGC
同上,新生代使用复制算法,老年代使用标记整理算法。
在这里插入图片描述
停下来以后(安全点),垃圾回收器会开启多个线程来进行gc
垃圾回收线程的个数与CPU核数相同
工作时候cpu使用曲线:占用率一下100%(4核都去回收垃圾了)

线程数的控制:-XX:ParallelGCThreads=n

  • 一些选项
-XX:+UseAdaptiveSizePolicy 动态调整新生代,调整EdenSurvival区的大小 

目标设定:
-XX:GCTimeRatio=ratio 调整吞吐量 垃圾回收时间/总时间 1/(1+ratio) 调整这个堆大小使其达到目标  一般设置19
-XX:MaxGCPauseMillis=ms 最大暂停毫秒数 

上面这两个目标是矛盾的><

3. 响应时间优先的垃圾回收器

  • 多线程
  • 堆内存较大,多核cpu
  • 尽可能让STW的时间最短
    0.1 0.1 0.1 0.1 0.1 ……
    开启:-XX:+UseConcMarkSweepGC
    concurrent 并发标记清除
    用户进程和垃圾清除进程可以同时工作,这样减少Stop the world的时间。
    cms工作在老年代的垃圾回收器

在这里插入图片描述

  • 工作流程
    工作在老年代垃圾回收的时候,标记清除算法(有内存碎片)
    初始标记的时候,仍然需要stop the world
    等到初始标记完成后,用户线程恢复运行,垃圾回收进程还能同时并发标记,这样响应时间是很短的。
    并发结束后,还需要重新标记,要stop the world;然后用户线程恢复运行,进行清理。

  • 补充

    • 初始标记时候受以下参数影响
      XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads
      一般4 1

    • 对CPU占用不高,四核cpu只取了1核做回收
      用户占用3/4 吞吐量下降一点点

    • 并发清理时候用户线程还在运行,还会产生垃圾,叫做浮动垃圾。要预留一些参数来保留浮动垃圾。参数:XX:CMSInitiatingOccupancyFraction=percent 内存占比,设置为80,就是指:老年代的空间已经占用80%时,就进行垃圾回收。这个值越小,CMS触发垃圾回收的时机越早

    • -XX:+CMSScavengeBeforeRemark在重新标记阶段,可能有新生代的对象引用老年代的对象。使用这个标签,在重新标记阶段之前,对新生代做一次垃圾回收。

    • 碎片过多,会并发失败,退化为串行垃圾回收,使得响应时间++

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值