垃圾回收相关概念

1、System.gc()

在默认的情况下,通过System.gc()或者Runtime.getRuntime().gc()的调用,会显式触发Full GC(),同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。但一般情况下,垃圾回收应该是自动进行的,除非一些特殊情况,如测试某模块性能前,可以先调用一次System.gc()

并且调用System.gc()并不是马上就会进行垃圾回收,而是告诉JVM,有回收垃圾的意向,具体是否调用由JVM决定。

使用代码进行测试:

public class TestSystemGC {

    public static void main(String[] args) {
        new TestSystemGC();
        System.gc();  //提醒JVM进行full GC,但不保证一定执行
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("调用了finalize方法");
    }
}

结果1,使用System.gc()成功触发了GC:

在这里插入图片描述

结果2,使用System.gc()没有触发GC

在这里插入图片描述

上述的测试证实了System.gc()并不能直接触发GC,而是提醒JVM进行GC,至于JVM执不执行GC,什么时候执行无从得知

2、内存溢出和内存泄漏

2.1 内存溢出

内存溢出是引发程序崩溃的罪魁祸首之一,内存溢出会直接引起OOM(OutOfMemoryError),即没有空闲内存,且垃圾收集器也无法提供更多内存空间

可能的原因:

  1. 堆内存设置不够
  2. 代码中创建了大量的大对象,且长时间不能被垃圾收集器收集
  3. 当创建新对象时发现内存不足后,垃圾收集器进行GC,GC执行完,内存依然不足

由于GC一直在发展,一般情况下,除非应用程序占用的内存增长速度非常快,造成GC释放内存空间的速度跟不上内存消耗的速度,否则不太容易出现OOM的情况,大多数情况下,GC会进行各年龄段的垃圾回收,实在不行,就进行一次Full GC,这时候会回收大量的内存,供应用程序继续使用

2.2 内存泄漏

内存泄漏即某对象不再被程序使用,但GC又无法回收它所占的空间,尽管内存泄漏并不会立刻引起程序崩溃,但是一旦发生内存泄漏,程序中可用的内存就会减少,直到所有内存耗尽,最终引起内存溢出,导致程序崩溃

由于JVM使用可达性分析算法,所有处于引用链上的对象都被视为可达的对象,无法被回收,但处于引用链上的某些对象可能不再被使用,但是由于没有断开指向它们的指针,导致它们仍然挂在引用链上,本应该被回收的垃圾被视为了可达对象,它们被占用的空间无法被释放,这就造成了内存泄露

在这里插入图片描述

如对于某些变量,可以在方法内定义成局部变量,但是却将其定义为成员,甚至是静态成员,这样做明显地增长了该变量的生命周期,但该变量只在某个方法内起作用,其所占用的空间在使用后迟迟不能释放,这种也可以称为宽泛意义上的内存泄漏。

例如:

  • 单例模式所生产的单例对象,它们的生命周期和应用程序一样长。在程序中,单例对象如果持有对外部对象的引用,那么这个外部对象的生命周期就和单例对象一样长,不能被回收,导致内存泄漏
  • 一些资源没有close()导致内存泄漏,如数据库连接,网络连接socket和io等必须手动关闭,否则不能被回收 导致内存泄漏

3、Stop The World的理解

STW指在GC过程中,会产生应用程序的停顿,停顿产生时,整个应用程序线程都会被暂停,这个停顿称为STW

标记垃圾阶段,使用的可达性分析算法会导致所有用户线程停顿,原因是:

  • 分析工作必须在一个能够确保一致性的快照中进行
  • 一致性指整个分析过程期间整个执行系统看起来像被冻结在某个时间
  • 如果出现分析过程中对象引用关系还在不断变话,则分析结果的正确性无法保证

被STW中断的应用程序线程会在完成GC之后恢复,频繁中断会让用户感觉像是网上不快造成电影卡带一样,所以我们需要减少STW的发生和STW产生停顿的时长

关于STW的几个说明:

  1. STW和采用哪款GC无关,所有的GC都会出现STW,只是越好的GC的STW的时间更短,间隔更长
  2. STW是JVM在后台自动发起和自动完成的。在用户不可见的情况下,把用户正常的工作线程全部停掉
  3. 开发中不要使用System.gc();会导致STW的发生

4、安全点和安全区域

程序在执行时,并非在所有地方都能停顿下来进行GC,只有在特定的位置才能停顿下来开始GC,这些位置称为“安全点”(Safe Point)

安全点的选择很重要,安全点太少可能导致GC等待的时间太长,太多可能导致程序运行时的性能问题,大部分指令的执行时间都非常短暂,通常会选择一些执行时间较长的指令作为安全点,如方法调用,循环跳转,异常跳转

如何在发生GC时,检查所有的线程都跑到了最近的安全点停顿下来呢?

  • 抢先式中断(已无虚拟机采用)
    • 首先中断所有线程,如果还有线程不在安全点,就恢复这些不在安全点的线程,让线程跑到安全点
  • 主动式中断
    • 设置一个中断标志,程序将准备进行GC时,中断标志变为真,各个线程运行到安全点时主动轮询这个标志, 如果中断标志为真,线程将自己中断挂起

安全点的机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的安全点,但是当程序“不执行”时,如线程处于Sleep状态或Block状态,这时线程无法响应JVM的中断请求,走到安全点将自己挂起,JVM也无法将这类线程唤醒,对于这种情况,就需要 安区区域(Safe Region) 来解决

安全区域指在一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始GC都是安全的,可以把安全区域看做被扩展的安全点

执行流程:

  1. 当线程运行到安全区域的代码时,首先标识已经进入了安全区域,如果这段时间内发生了GC,JVM会忽略标识为安全区域状态的线程
  2. 当线程即将离开安全区域时,会检查JVM是否已经完成GC,如果完成了,则继续运行,否则线程必须等待直到收到可以安全离开安全区域的信号为止

5、JAVA中的引用

在Java中将引用分为强引用(Strong Reference),软引用(Soft Reference),弱引用(Weak Reference),虚引用(Phantom Reference)这4种引用强度逐渐减弱

  • 强引用(Strong Reference)有引用不回收

    代码中普遍存在的引用,如我们经常使用的new对象的操作就是建立了强引用,无论任何情况下,只要强引用关系还在,垃圾收集器就不会回收被引用的对象。

  • 软引用(Soft Reference)空间不够才回收

    在系统将要发生内存溢出前,把软引用关联的对象列入回收范围进行第二次回收,如果回收后还没有足够的内存,抛出OOM

  • 弱引用(Week Reference)发生垃圾收集就回收

    被弱引用关联的对象只会存活在下一次垃圾收集之前

    当发生GC,无论空间是否足够,都会回收掉被软引用关联的对象

  • 虚引用(Phantom Reference)虚引用回收不影响其对象,并发出通知

    一个对象是否有虚引用的存在 完全不会对其生存时间构成影响,也无法通过虚引用获得一个对象的实例

    为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收时收到一个系统通知

5.1 强引用Strong Reference

Java程序中,99%的引用类型是强引用,也就是最常见的普通对象引用,也是默认的引用类型,使用new操作符创建一个新的对象,并将其赋值给一个变量时,这个变量就称为指向该对象的一个强引用

当强引用的对象仍挂在引用链上时,GC就不会回收它,为此强引用是Java内存泄漏的主要原因之一

强引用的特点:

  1. 强引用可以直接访问目标对象
  2. 强引用的对象只要仍能被GC Root触及,就不会被回收
  3. 强引用可能导致内存泄漏

5.2 软引用 Soft Reference

软引用被用来描述一些还有用,但非必须的对象,被软引用关联的对象再系统将要抛出OOM前,将会被列入回收范围,进行第二次回收,将可达的软引用对象也进行回收,回收后内存空间还不足就会抛OOM

/**
 * 测试软引用:空间不够才回收
 * 参数 :-Xmx10M -Xms10M -XX:+PrintGCDetails
 */
public class TestSoftReference {
    public static class User{
        private int id;
        private String name;

        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }

        @Override
        public String toString() {
            return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
        }
    }

    public static void main(String[] args) {
        //添加一个弱引用
        SoftReference<User> softRef = new SoftReference<>(new User(1, "张三"));

        //获取弱引用
        System.out.println(softRef.get());

        // full Gc一下
        System.gc(); //软引用被放到了老年代

        // 再获取一次
        System.out.println(softRef.get());
        System.out.println("--------------------------");


        //加入一个大对象,出现堆内存溢出Java heap space,此时弱引用被回收
        try {
            byte[] bytes = new byte[1024 * 1024 * 7];
        }catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(softRef.get());
        }
    }
}

软引用通常迎来实现内存敏感的缓存,如高速缓存就有用到软引用,如果还有空闲内存,就暂时保留缓存,当内存不足时再清理掉,这样就保证了使用缓存的同时,不会耗尽内存

软引用的特点:

  1. 当内存足够时,不回收软引用的可达对象
  2. 内存不足时,回收软引用的可达对象

5.3 弱引用 Week Reference

弱引用也是用来描述那些非必须对象,只被弱引用关联的对象只能生存到下一次GC发生为止,在系统GC到来时,只要该对象是弱引用,不管堆空间是否足够,不管该对象是否可达,都会直接回收

但是由于GC线程的优先度通常很低,因此并不一定能很快发现持有弱引用的对象,这种情况下,弱引用对象可以存在较长的时间

软引用和弱引用都非常适合保存那些可有可无的缓存数据,当系统内存不足时,这些缓存数据会被回收,不会导致内存溢出,当内存充足时,这些数据又可以存在较长的时间,从而加速系统

/**
 * 测试软引用
 * @Author :漠殇
 * @Data :Create in 21:49 2022/6/15
 */
public class TestWeakReference {
    public static class User{
        private int id;
        private String name;

        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }

        @Override
        public String toString() {
            return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
        }
    }

    public static void main(String[] args) {
        WeakReference<User> weakRef = new WeakReference<>(new User(1, "张三"));

        System.out.println(weakRef.get());

        System.gc();

        System.out.println(weakRef.get());
    }
}


//输出
//User{id=1, name='张三'}
//null

5.4 虚引用 Phantom Reference

虚引用是所有引用类型中最弱的一个,一个对象是否有虚引用的存在,完全不会决定该对象的生命周期,如果一个对象仅持有虚引用,那么它和没有引用几乎是一样的,随时可能被当作垃圾回收

虚引用不能单独使用,也无法通过虚引用获取被引用的对象,当使用虚引用的get()方法试图获取对象时,结果总是null,为一个对象设置虚引用的唯一目的就是跟踪垃圾回收过程,当持有虚引用的对象被回收后,会收到一个系统通知

虚引用在创建时,必须要和引用队列一起使用,当垃圾收集器准备回收一个对象时,发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,以通知应用程序该对象已被回收

/**
 * 测试虚引用
 */
public class TestPhantomReference {
    public static TestPhantomReference obj;
    static ReferenceQueue<TestPhantomReference> phantomQueue = null; //引用队列

    public static class CheckRefQueue extends Thread {
        @Override
        public void run() {
            while (true) {
                if (phantomQueue != null) {
                    PhantomReference<TestPhantomReference> objs = null;

                    try {
                        objs = (PhantomReference<TestPhantomReference>) phantomQueue.remove();
                    } catch (Throwable e) {
                        e.printStackTrace();
                    }

                    if (objs != null) {
                        System.out.println("追踪垃圾回收过程,TestPhantomReference实例被GC了");
                    }
                }
            }
        }
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize");
        obj = this;
    }

    public static void main(String[] args) {
        Thread t = new CheckRefQueue();
        t.setDaemon(true); //设为守护线程
        t.start();


        phantomQueue = new ReferenceQueue<>();
        obj = new TestPhantomReference();

        PhantomReference<TestPhantomReference> phantomRef = new PhantomReference<>(obj, phantomQueue);

        try {
            System.out.println(phantomRef.get());

            // 去掉强引用
            obj = null;

            System.gc(); //第一次GC对象可复活,无法回收

            Thread.sleep(1000);

            if (obj == null) {
                System.out.println("obj is null");
            } else {
                System.out.println("obj not null");
            }

            obj = null; // 第二次,一旦obj被回收,就会将虚引用存放在队列中

            System.gc(); //第二次GC
            Thread.sleep(1000);
            if (obj == null) {
                System.out.println("obj is null");
            } else {
                System.out.println("obj not null");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/*
null
finalize
obj not null
追踪垃圾回收过程,TestPhantomReference实例被GC了
obj is null
*/

特点:无法单独使用,不影响对象的生命周期,无法通过虚引用获取对象实例。只能跟踪垃圾回收过程 当持有虚引用的对象被回收后可以收到一个系统通知

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值