(二)JVM垃圾回收

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

1.1引用计数法

只要一个对象被其他变量所引用,让对象的技术加1。如果不再引用,那么就让他的计数减一。引用计数变为0时,当作垃圾回收。
在这里插入图片描述

但是像这种循环引用的,其引用计数不能归零,无法垃圾回收。

1.2 可达性分析算法

所以所有的可达性算法都会有起点,那么这个起点就是GC Root。
也就是需要通过GC Root 找出所有活的对象,那么剩下所有的没有标记的对象就是需要回收的对象。

首先要确定一系列根对象(那些肯定不能当成垃圾的被回收的对象)
垃圾回收之前对堆内存中的所以对象进行一遍扫描,看看每一个对象是否被根对象引用,如果是就不能被回收,反之可以回收。
在这里插入图片描述

  • 利用可视化工具找GC root
    工具:Memory Analyzer(MAT)
    1.查看进程id
    在这里插入图片描述
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020092810120
  • List item

2978.png#pic_center)
2.抓取堆内存快照
在这里插入图片描述

什么对象可以作为GC root的对象?

  • a.java虚拟机栈中的引用的对象

在这里插入图片描述

  • b.方法区中的类静态属性引用的对象(一般指被static修饰的对象,加载类的时候就加载到内存中。)
    在这里插入图片描述

  • c.方法区中的常量引用的对象
    在这里插入图片描述

  • d.本地方法栈中的JNI(native方法)引用的对象

  • 注意
    在这里插入图片描述

1.3四种引用

1.1分类
强引用 弱引用 虚引用 终结器引用
图中实线为强引用 虚线为其他引用
在这里插入图片描述

  • 强引用:只要能够通过GC Root的引用链找到就不会被垃圾回收,也就是说只有所有的GC Roots对象都不通过强引用引用该对象的时候,该对象才能被垃圾回收

  • 弱引用:如果某个对象与弱引用关联且无强引用,那么当JVM在进行垃圾回收时,无论内存是否充足,都会回收此类对象。

  • 软引用:java中使用SoftRefence来表示软引用,如果某个对象与软引用关联(前提是没有强引用引用他的时候),那么JVM只会在内存不足的情况下回收该对象

  • 虚引用:相当于无引用,使对象无法被使用,必须与引用队列配合使用创建的时候会关联一个引用队列
    例如:创建ByteBuffer的时候会创建一个名为Cleaner的虚引用对象,当ByteBuffer没有被强引用所引用就会被jvm垃圾回收,虚引用Cleaner就会进入引用队列,会有专门的线程扫描引用队列,被发现后会调用直接内存地址的方法将直接内存释放掉,保证直接内存不会导致内存泄漏
    是所有引用类型中最弱的,一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用获得一个对象实例。

  • 终结器引用:创建的时候会关联一个引用队列,当A4对象没有被强引用所引用时,A4被垃圾回收的时候,会将终结器引用放入到一个引用队列(被引用对象暂时还没有被垃圾回收),有专门的线程(优先级较低,可能会造成对象迟迟不被回收)扫描引用队列并调用finallize()方法,第二次GC的时候才能回收掉被引用对象。
    这也就是我们为什么不推荐finallize()方法释放资源的理由

实例:
1.强引用的使用
设置JVM的内存为20M -Xmx20M
在这里插入图片描述
在这里插入图片描述
2.软引用:
一些不重要的资源我们可以用软引用去关联它,
输入jvm指令:
在这里插入图片描述

public class Test2 {
    private static final int _4MB=4*1024*1024;
    public static void main(String[] args) throws IOException {
        soft();
    }
    public static void soft(){
        List<SoftReference<byte[]>> list=new ArrayList<>();
        for(int i=0;i<5;i++){
            SoftReference<byte[]> reference=new SoftReference<>(new byte[_4MB]);
            System.out.println(reference.get());
            list.add(reference);
            System.out.println(list.size());
        }
        System.out.println("循环结束:"+list.size());
        for(SoftReference<byte[]> reference:list){
            System.out.println(reference.get());
        }
    }
}

运行结果

[B@1b6d3586
1
[B@4554617c
2
[B@74a14482
3
[GC (Allocation Failure) [PSYoungGen: 1744K->488K(6144K)] 14032K->12932K(19968K), 0.0427814 secs] [Times: user=0.00 sys=0.00, real=0.05 secs] 
[B@1540e19d
4
[GC (Allocation Failure) --[PSYoungGen: 4696K->4696K(6144K)] 17140K->17140K(19968K), 0.0030222 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 4696K->4574K(6144K)] [ParOldGen: 12444K->12414K(13824K)] 17140K->16988K(19968K), [Metaspace: 3118K->3118K(1056768K)], 0.0095409 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) --[PSYoungGen: 4574K->4574K(6144K)] 16988K->16996K(19968K), 0.0010492 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 4574K->0K(6144K)] [ParOldGen: 12422K->587K(8704K)] 16996K->587K(14848K), [Metaspace: 3118K->3118K(1056768K)], 0.0076471 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
[B@677327b6
5
循环结束:5
null
null
null
null
[B@677327b6
Heap
 PSYoungGen      total 6144K, used 4546K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
  eden space 5632K, 80% used [0x00000000ff980000,0x00000000ffdf0940,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 8704K, used 587K [0x00000000fec00000, 0x00000000ff480000, 0x00000000ff980000)
  object space 8704K, 6% used [0x00000000fec00000,0x00000000fec92d20,0x00000000ff480000)
 Metaspace       used 3224K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 349K, capacity 388K, committed 512K, reserved 1048576K

可以看出,在第四次和第五次的时候就触发了垃圾回收,在对集合进行遍历后只有第五个软引用对象存在,也就是说在内存不足的时候将前面的虚引用的对象进行了垃圾回收

引用队列:

public class Test2 {
    private static final int _4MB=4*1024*1024;
    public static void main(String[] args) throws IOException {
        List<SoftReference<byte[]>> list=new ArrayList<>();
        //引用队列
        ReferenceQueue<byte[]> queue=new ReferenceQueue<>();
        for(int i=0;i<5;i++){
            //关联了引用队列,当软引用所关联的byte数组被回收时,软引用自己会加入到引用队列中
            SoftReference<byte[]> reference=new SoftReference<>(new byte[_4MB],queue);
            System.out.println(reference.get());
            list.add(reference);
            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());
        }
    }
}

运行结果:
在这里插入图片描述
2.弱引用

public class Test2 {
    private static final int _4MB = 4 * 1024 * 1024;

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

运行结果:
在这里插入图片描述

2.垃圾回收算法

2.1标记清除

在这里插入图片描述
速度快,但是会产生内存碎片

2.2标记整理

在这里插入图片描述
使得不被清理的对象,紧凑到一边
无内存碎片,由于整理对象时需要移动对象,效率低。

2.3复制算法

把内存区域划成大小相对两块FROM和TO
垃圾清理时,把存活的对象从FROM赋值到TO上。清理FROM,并且交换FROM和TO

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

3分代垃圾回收

在虚拟机中,采用分代的思想,将上面三种算法结合起来,让他们协同工作
在这里插入图片描述
长时间使用的对象放在老年代,用完了就可以丢弃的放在新生代。这样就可以根据不同对象的生命周期特点,执行不同的算法。

Minor GC (新生代空间不足时)
在这里插入图片描述
在幸存区中的对象的生命值达到一定的阈值后,就会把他放在老年代中。

Full GC (老年代空间不足时)
总结

在这里插入图片描述

  • 对象首先分配在伊甸园区域
  • 新生代空间不足,出发Minor GC,伊甸园和from存活的对象使用copy复制到to中,存活的对象年龄加1并且交换from to;
  • Minor GC 会引发stop the world(线程礼让,当发生垃圾回收时其他的线程都要先停下来,让GC先行。因为在GC时from to 会发生对象的复制),暂停其他用户的线程,等垃圾回收结束,用户线程菜恢复运行。
  • 当对象寿命超过阈值时,会晋升至老年代。寿命最大是15次
  • 当老年代空间不足,会先尝试出发MinorGC,如果之后空间仍不足,那么出发Full GC。

3.1 相关VM参数

4.垃圾回收器

4.1串行

  • 单线程,GC时其他线程都暂停
  • 堆内存较小,适合个人电脑
    使用语句:
    在这里插入图片描述

其中serial工作在新生代,采用的算法时复制
SerialOld工作在老年代,标记+整理算法

4.2吞吐量优先

  • 多线程
  • 堆内存较大,多核cpu来支持
  • 让单位时间内,STW的时间最短 0.2 0.2=0.4(垃圾回收占比时间越低,吞吐量越高)
    在这里插入图片描述
    -XX:UseAdaptiveSizePolicy 动态调整
    -XX:GCTimeRatio=ratio 1/(1+ratio)垃圾回收所占的工作时间
    -XX:MaxGCPauseMillis=ms 垃圾回收时的最大暂停时间
    -XX:ParallelGCThread=n 启用垃圾回收的线程数

4.3响应时间优先

  • 多线程
  • 堆内存较大,多核cpu来支持
  • 尽可能让单次STW的时间最短 0.1 0.1 0.1 0.1 0.1=0.5
    它是并发的(concurrent)GC线程和用户线程同时工作
    在这里插入图片描述
    在这里插入图片描述

4.4G1

Garbage First
在这里插入图片描述
(从JDK9开始,G1就取代了之前的CMS垃圾回收器)
在这里插入图片描述
在这里插入图片描述

1.回收阶段

在这里插入图片描述

2.Young Conllection

Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)

在这里插入图片描述
在这里插入图片描述
(内存紧张了复制进幸存区)
G1把堆内存都划分为了一个个的区域,每一个region都可以独立当作伊甸园、幸存区和老年代
在这里插入图片描述
(幸存区对象进入老年代)

3.Young Collection + CM(并发标记)

在这里插入图片描述

4.Mixed Collection

在这里插入图片描述

5.Full GC

在这里插入图片描述
其中CMS和G1老年代内存不足,分两种情况,对于G1当老年代的内存超过阈值的时候就会触发并发标记和混合收集,如果回收的速度高于用户线程产生垃圾的速度的话,这个时候还处于并发垃圾收集的阶段,还不是Full GC。当垃圾回收的速度赶不上垃圾收集的速度,就会退化为串行的收集,触发Full GC

CMS是并发收集失败的时候才会触发Full GC

6.Young Collection 跨代引用

  • 新生代回收的跨代引用(老年代引用新生代)问题
    在这里插入图片描述

7.JDK 8u20 字符串去重

在这里插入图片描述

8.并发标记类卸载

所有对象经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类不再使用,则卸载它所加载的所有类

  • XX:+ClassUnloadingWithConcurrentMark 默认启用

9.回收巨型对象

(1)定义
一个对象大于region的一半时,称之为巨型对象
(2)特征

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

10.并发标记起始时间的调整

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值