JVM垃圾回收篇(对象引用)

Posted 编程小吉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM垃圾回收篇(对象引用)相关的知识,希望对你有一定的参考价值。

对象引用

1.什么是对象引用?

  • 在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference) 、弱引用(Weak Reference) 和虚引用(Phantom Reference) 4种类型,并且这4种引用的强度依次逐渐减弱

  • 除强引用外,其它的引用均可以在 java.lang.ref 包中找到它们的实现类,其中只有终结器引用FinalReference是包内可见的,不带有修饰符public

强引用、软引用、弱引用、虚引用有什么区别?具体使用场景又是什么?

  • 强引用:它是最传统的“引用”定义,是指在程序代码中普遍存在的引用赋值,即类似 Object obj = new Object()这种引用关系。无论在任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。99%的场景下都是强引用
  • 软引用:在系统将将要发生内存溢出之前,将会把这些对象列入回收范围之中进行第二次回收,只有当这次回收后还没有足够的内存,才会发生内存溢出。通常在缓存的场景下使用
  • 弱引用:被弱引用关联的对象只能生存到下一次垃圾收集之前,当垃圾收集器工作时,无论内存空间是否足够,都会回收掉被弱引用关联的对象。通常在缓存的场景下使用
  • 虚引用:一个对象是否有虛引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象的实例,它的主要目的就是能在这个对象被收集器回收的时候接收到一个通知信息。通常在对象回收跟踪分析的场景下使用

2.强引用

  • 在Java程序中,最常见的引用类型就是强引用,它也是默认的引用类型
  • 在Java语言中使用new操作符创建一个新的对象,并将其赋值给一个变量的时候,这个变量就成为指向该对象的一个强引用
  • 强引用的对象是可触及的,垃圾收集器永远不会回收这些对象。相对的,软引用、 弱引用、虚引用的对象是软可触及、弱可触及、虛可触及的,在一定条件下都是可以被垃圾收集器回收的
  • 强引用可以直接访问目标对象
  • 强引用所指向的对象在任何时候都不会被系统回收,即便出现内存溢出
  • 强引用可能导致内存泄漏

3.软引用

  • 软引用是用来描述一些还有用,但又非必需的对象。也就是说,在系统将要发生内存溢出之前,则会把这些软引用对象进行第二次回收,所谓第一次回收是将不可触及对象的回收
  • 软引用通常用来实现内存敏感的缓存,比如在高速缓存中就有用到软引用,如果还有空闲内存,就可以暂时保留缓存,但当内存不足时,则清理掉缓存
  • 软引用可以直接访问目标对象
  • 软引用所指向的对象,在内存够的时候不会被系统回收,在内存不够的时候会被系统回收

代码示例

/**
 * 设置堆内存大小
 * -Xms10m -Xmx10m
 */
public class SoftReferenceTest 
    public static class User 
        public User(int id, String name) 
            this.id = id;
            this.name = name;
        

        public int id;
        public String name;

        @Override
        public String toString() 
            return "[id=" + id + ", name=" + name + "] ";
        
    

    public static void main(String[] args) 
        // 创建对象并建立软引用
		SoftReference<User> userSoftRef 
            = new SoftReference<User>(new User(1, "张三"));

        // 从软引用中获取引用对象,这里的get()方法会调用对象的toString()方法
        System.out.println(userSoftRef.get());

        // 进行一次垃圾回收,由于堆空间内存足够,所以软引用依旧存在
        System.gc();
        System.out.println(userSoftRef.get());

        try 
            // 让堆空间内存不够(出现OOM异常),或者让堆空间内存即将不够(不出现OOM异常)
			byte[] b = new byte[1024 * 1024 * 7];
         catch (Throwable e) 
            e.printStackTrace();
         finally 
            // 由于堆空间内存不够,所以软引用被回收
            System.out.println(userSoftRef.get());
        
    

4.弱引用

  • 弱引用也是用来描述那些非必需的对象,这些对象只能生存到下一次垃圾收集发生为止,也就是说在系统进行垃圾回收的时候,不管系统堆空间内存是否充足,只要发现弱引用都会将它关联的对象回收
  • 但是,由于垃圾回收器的线程通常优先级很低,因此弱引用对象并不一定能够很快被发现,这种情况下弱引用对象可以存活较长的时间
  • 弱引用和软引用一样,在构造弱引用时也可以指定一个引用队列,当弱引用对象被回收时,就会加入指定的引用队列,通过这个队列可以跟踪对象的回收情况
  • 弱引用和软引用一样,都非常适合来保存那些可有可无的缓存数据。当系统内存不足时,这些缓存数据会被回收,而当内存资源充足时,这些缓存数据又可以存在相当长的时间,从而起到加速系统的作用
  • 弱引用可以直接访问目标对象
  • 弱引用所指向的对象,不管当前内存够还是不够,都会被系统回收

代码示例

public class WeakReferenceTest 
    public static class User 
        public User(int id, String name) 
            this.id = id;
            this.name = name;
        

        public int id;
        public String name;

        @Override
        public String toString() 
            return "[id=" + id + ", name=" + name + "] ";
        
    

    public static void main(String[] args) 
        // 创建对象并建立弱引用
        WeakReference<User> userWeakRef 
            = new WeakReference<User>(new User(1, "张三"));
        
        // 从弱引用中获取引用对象,这里的get()方法会调用对象的toString()方法
        System.out.println(userWeakRef.get());

        // 进行一次垃圾回收,弱引用被回收
        System.gc();
        System.out.println(userWeakRef.get());
    

5.虚引用

  • 虚引用,也称为“幽灵引用”或者“幻影引用”,是所有引用类型中最弱的一个
  • 一个对象是否有虚引用的存在,完全不会决定对象的生命周期,也就是说这个引用有和没有几乎是一样的,随时都可能被垃圾回收器回收
  • 虚引用必须和引用队列一起使用,在创建时必须提供一个引用队列作为参数,当垃圾回收器准备回收一个对象时,如果发现它还有虛引用,就会在回收对象后,将这个虚引用加入引用队列,以便后续通知应用程序对象的回收情况。正是由于虚引用可以跟踪对象的回收情况,所以也可以将一些资源释放操作放置在虛引用中执行和记录
  • 虚引用不可以直接访问目标对象
  • 虚引用的唯一目的在于可以跟踪垃圾回收过程

代码示例

public class PhantomReferenceTest 
    // 对象
    public static PhantomReferenceTest obj;
    // 引用队列
    public static ReferenceQueue<PhantomReferenceTest> phantomQueue = null;
	
    // 查看引用队列内容的线程
    public static class CheckRefQueue extends Thread 
        @Override
        public void run() 
            while (true) 
                if (phantomQueue != null) 
                    PhantomReference<PhantomReferenceTest> objt = null;
                    try 
                        objt = (PhantomReference<PhantomReferenceTest>) 
                            								phantomQueue.remove();
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                    if (objt != null) 
                        System.out.println("PhantomReferenceTest实例被回收了");
                    
                
            
        
    

    public static void main(String[] args) 
        // 开启线程
        Thread t = new CheckRefQueue();
        t.setDaemon(true);
        t.start();

        // 创建虚引用
        phantomQueue = new ReferenceQueue<PhantomReferenceTest>();
        obj = new PhantomReferenceTest();
        PhantomReference<PhantomReferenceTest> phantomRef 
            = new PhantomReference<PhantomReferenceTest>(obj, phantomQueue);

        try 
            // 从虚引用中获取引用对象,这里获取不到
            System.out.println(phantomRef.get());

            // 将强引用去除并进行垃圾回收,此时对象会被放入引用队列中去
            obj = null;
            System.gc();
         catch (InterruptedException e) 
            e.printStackTrace();
        
    

6.终结器引用

  • 它用以实现对象的 finalize()方法,所以称之为终结器引用
  • 它无需手动编码, 其内部配合引用队列使用
  • 它由垃圾收集器Finalizer线程通过终结器引用找到被引用对象并调用它的finalize()方法,在第二次垃圾回收时才能回收被引用对象

以上是关于JVM垃圾回收篇(对象引用)的主要内容,如果未能解决你的问题,请参考以下文章

JVM升华篇

深入理解JAVA虚拟机之JVM性能篇---垃圾回收

JVM | 垃圾回收

如何优雅的学习JVM,升华篇

如何优雅的学习JVM,升华篇

JVM垃圾回收的过程