Java引用类型

  • 应该说,引用对于对象来说是至关重要的,对象生来是要被用的,没有引用的对象自然就用不到了,也就面临被回收,判断对象存活的关键在于引用

引用的分类

  • JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用;JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用,终结器引用五种(引用强度逐渐减弱)。垃圾回收器对于不同引用强度的引用有不同的回收策略

    • 对象当然可以对应多种类型的引用,每个引用都会发挥其对应的作用(比如与各种队列的关联),但是在回收策略上由强度最高的引用决定

强引用

  • 强引用是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,JVM宁愿抛出 OOM 错误,使程序终止,也不会靠随意回收具有强引用的对象来解决内存不足问题
  • 对于强引用来说,唯一使得其实例主动被回收的方法就是向强引用赋值null

    • 为强引用赋值null以促成JVM垃圾回收的最典型的案例就是ArrayList的clear方法

      public void clear() {
        modCount++;
      
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
          elementData[i] = null;
      
        size = 0;
      }
      • 释放数组成员的内存,但是不释放数组的内存,因为后续可能还会使用,只是清除存储的对象数据,但是不回收已经分配的数组空间
  • 与其他几种引用类型相比,强引用没有对应的类型定义,也没有引用队列使用,毕竟是古老的默认的引用,同时作为最强的引用也没必要有这些

软引用

  • 如果一个对象只具有软引用,则类似于非必须生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。软引用可用来实现内存敏感的高速缓存,可加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生
  • 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中

    public class SoftReferenceDemo {
    
        public static void main(String[] args) {
            Obj obj = new Obj();
            SoftReference<Obj> sr = new SoftReference<Obj>(obj);
            obj = null;
            System.out.println(sr.get());
        }
    }
    
    class Obj {
    
        int[] obj;
    
        public Obj() {
            obj = new int[1000];
        }
    }
    • 使用SoftReference描述软引用
  • 使用软引用的一个例子:假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取,则会严重影响性能,但是如果全部加载到内存当中,又有可能造成内存溢出,此时使用软引用可以解决这个问题:使用软引用维系内存中的图片数据,如果空间够用就正常使用,如果内存不够了,垃圾回收器会回收内存中图片数据占用的空间

弱引用

  • 如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
  • 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,JVM就会把这个弱引用加入到与之关联的引用队列中

    public class WeakReferenceDemo {
    
        public static void main(String[] args) {
          
            WeakReference<String> sr = new WeakReference<>(new String("hello"));
            System.out.println(sr.get()); // hello
            System.gc();                //通知JVM的gc进行垃圾回收
            System.out.println(sr.get()); // null 对饮的实例已经被回收
        }
    }
    • 使用WeakReference描述弱引用
ThreadLocal
  • 关于经典的ThreadLocal + 弱引用导致的内存泄漏在ThreadLocal原理的文章中介绍

虚引用

  • 虚引用主要用来跟踪对象被垃圾回收的活动,给程序以指示以在对象的内存被回收前采取必要的行动。或者说一个对象持有虚引用,实际上就是等待被回收的对象
  • 虚引用与软引用和弱引用的一个区别在于: 虚引用必须和引用队列(ReferenceQueue)联合使用,并且无法通过该引用来获取对象
  • 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动

    public class PhantomReferenceDemo {
    
        public static void main(String[] args) {
            ReferenceQueue<String> queue = new ReferenceQueue<String>();
            // 虚引用必须指定一个ReferenceQueue
            PhantomReference<String> pr = new PhantomReference<String>(new String("hello"), queue);
            System.out.println(pr.get());  // null
        }
    }
    • 使用PhantomReference描述虚引用
  • 关于虚引用的一个经典的使用案例是DirectByteBuffer类中定义的Cleaner类(继承自PhantomReference),Cleaner类可以借用虚引用的特性保证DirectByteBuffer实例被回收后,也可以同步回收由此类分配的堆外内存
DirectByteBuffer
  • DirectByteBuffer是Java用于实现堆外内存的一个重要类,通常用在通信过程中做缓冲池,如在Netty、MINA等NIO框架中应用广泛。DirectByteBuffer对于堆外内存的创建、使用、销毁等逻辑均由Unsafe提供的堆外内存API来实现

    protected static final Unsafe unsafe = Bits.unsafe();
    
    DirectByteBuffer(int cap) {                   // package-private
    
      super(-1, 0, cap, cap);
      boolean pa = VM.isDirectMemoryPageAligned();
      int ps = Bits.pageSize();
      long size = Math.max(1L, (long)cap + (pa ? ps : 0));
      Bits.reserveMemory(size, cap);
    
      long base = 0;
      try {
        // 分配内存,返回基地址
        base = unsafe.allocateMemory(size);
      } catch (OutOfMemoryError x) {
        Bits.unreserveMemory(size, cap);
        throw x;
      }
      // 内存初始化
      unsafe.setMemory(base, size, (byte) 0);
      if (pa && (base % ps != 0)) {
        // Round up to page boundary
        address = base + ps - (base & (ps - 1));
      } else {
        address = base;
      }
      // 构建Cleaner对象用于跟踪DirectByteBuffer对象的垃圾回收,以实现当DirectByteBuffer被垃圾回收时,分配的堆外内存一起同步被释放
      cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
      att = null;
    }
  • 如何通过构建垃圾回收追踪对象Cleaner实现堆外内存释放的?----虚引用的典型应用

    // Cleaner继承虚引用
    public class Cleaner extends PhantomReference<Object> {
    
    // 虚引用必须结合虚引用队列使用
    private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue();
    // ....
      
    // DirectByteBuffer类初始化Cleaner类的语句
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    
    // Cleaner类提供的初始化方法
    public static Cleaner create(Object var0, Runnable var1) {
      return var1 == null ? null : add(new Cleaner(var0, var1));
    }
      
    // var1是DirectByteBuffer的引用,var2是Deallocator实例
    private Cleaner(Object var1, Runnable var2) {
      // 传递给虚引用的构造函数,至此,DirectByteBuffer拥有两个类型的引用,一个是调用DirectByteBuffer类的上层类中持有的强引用,一个是Cleaner中持有(本质上是被其父类的父类Reference持有)的虚引用
      super(var1, dummyQueue);
      this.thunk = var2;
    }
    
    // Cleaner内部维护了一个简单的链表
    private static synchronized Cleaner add(Cleaner var0) {
      if (first != null) {
        var0.next = first;
        first.prev = var0;
      }
    
      first = var0;
      return var0;
    }
      
    public void clean() {
        if (remove(this)) {
          try {
            this.thunk.run();
          } catch (final Throwable var2) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
              public Void run() {
                if (System.err != null) {
                  (new Error("Cleaner terminated abnormally", var2)).printStackTrace();
                }
    
                System.exit(1);
                return null;
              }
            });
          }
        }
      }
      
    // ...
      
    }
    
    // 执行堆外内存的回收的线程任务
    private static class Deallocator
            implements Runnable
        {
    
            private static Unsafe unsafe = Unsafe.getUnsafe();
    
            private long address;
            private long size;
            private int capacity;
    
            private Deallocator(long address, long size, int capacity) {
                assert (address != 0);
                this.address = address;
                this.size = size;
                this.capacity = capacity;
            }
                    
                  // 在clean方法中被调用
            public void run() {
                if (address == 0) {
                    // Paranoia
                    return;
                }
                // 释放内存
                unsafe.freeMemory(address);
                address = 0;
                Bits.unreserveMemory(size, capacity);
            }
        }
    • Cleaner继承PhantomReference,PhantomReference必须与引用队列ReferenceQueue结合使用,可以实现虚引用关联对象被垃圾回收时能够进行系统通知、资源清理等功能
    • 当某个被Cleaner引用的对象(DirectByteBuffer)将被回收时(DirectByteBuffer的强引用已经无效,只剩下虚引用,这个虚引用的类型其实就是Cleaner(继承自PhantomReference)),JVM垃圾收集器会将此对象的引用放入到对象引用中的pending链表(不是dummyQueue,pending列表是GC或者说是JVM维护的,pending列表中的非Cleaner的引用才会放入到对应的引用队列中)中,等待Reference Handler进行相关处理。其中,Reference Handler为一个拥有最高优先级的守护线程,会循环不断的处理pending链表中的对象引用,执行Cleaner的clean方法进行相关清理工作

      • Reference Handler的处理过程参考下边的Reference类的解析

    image-20210522172947851

终结器引用

  • 终结器引用FinalReference与前边几种引用一样都是属于Reference这个抽象类的子类其作用在于其唯一子类Finalizer在类加载时创建了执行finalize方法的线程,用于在GC回收前执行回收方法

    final class Finalizer extends FinalReference<Object> {
      private static class FinalizerThread extends Thread {
            private volatile boolean running;
            FinalizerThread(ThreadGroup g) {
                super(g, null, "Finalizer", 0, false);
            }
            public void run() {
                // in case of recursive call to run()
                if (running)
                    return;
    
                // Finalizer thread starts before System.initializeSystemClass
                // is called.  Wait until JavaLangAccess is available
                while (VM.initLevel() == 0) {
                    // delay until VM completes initialization
                    try {
                        VM.awaitInitLevel(1);
                    } catch (InterruptedException x) {
                        // ignore and continue
                    }
                }
                final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
                running = true;
                for (;;) {
                    try {
                          // 从队列中取出引用执行finalize方法
                        Finalizer f = (Finalizer)queue.remove();
                        f.runFinalizer(jla);
                    } catch (InterruptedException x) {
                        // ignore and continue
                    }
                }
            }
        }
      // ...
      static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread finalizer = new FinalizerThread(tg);
        finalizer.setPriority(Thread.MAX_PRIORITY - 2);
        finalizer.setDaemon(true);
        finalizer.start();
      }
      // ...
    }
  • 在可达性分析算法中,不可达对象默认的被JVM赋予FinalReference引用(实际上是Finalizer类型)并投喂到对应的一个ReferenceQueue中,等待Finalizer线程去执行此队列中的对象的finalize方法(在finalizer线程中,判断是否需要执行finalize方法)

Reference类

  • 除了强引用之外的其他四种引用实际上都以Reference类作为父类,而该类中定义了引用分类的核心功能
// Reference类
static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
  
              // 创建高优先级的Reference Handler线程
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        /* If there were a special system-only priority greater than
         * MAX_PRIORITY, it would be used here
         */
  
              // 最高优先级
        handler.setPriority(Thread.MAX_PRIORITY);
        // 守护进线程
        handler.setDaemon(true);
        // 线程启动
        handler.start();

        // provide access in SharedSecrets
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                return tryHandlePending(false);
            }
        });
    }



 /* High-priority thread to enqueue pending References  高优先级的守护线程用来处理GC向pending列表中投入的已经不可达的引用
  */
private static class ReferenceHandler extends Thread {

        private static void ensureClassInitialized(Class<?> clazz) {
            try {
                Class.forName(clazz.getName(), true, clazz.getClassLoader());
            } catch (ClassNotFoundException e) {
                throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
            }
        }

        static {
            // pre-load and initialize InterruptedException and Cleaner classes
            // so that we don't get into trouble later in the run loop if there's
            // memory shortage while loading/initializing them lazily.
            ensureClassInitialized(InterruptedException.class);
            ensureClassInitialized(Cleaner.class);
        }

        ReferenceHandler(ThreadGroup g, String name) {
            super(g, name);
        }

        public void run() {
            while (true) {
                tryHandlePending(true);
            }
        }
    }

// 此属性承载着pending list中的下一个pending的reference,由JVM维护
private static Reference<Object> pending = null;


// 高优先级的引用处理线程会死循环的执行此方法
static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            synchronized (lock) {
                if (pending != null) {
                    r = pending;
                    // 'instanceof' might throw OutOfMemoryError sometimes
                    // so do this before un-linking 'r' from the 'pending' chain...
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    // unlink 'r' from 'pending' chain
                    pending = r.discovered;
                    r.discovered = null;
                } else {
                    // The waiting on the lock may cause an OutOfMemoryError
                    // because it may try to allocate exception objects.
                    if (waitForNotify) {
                        // 等待pending list有reference后被JVM唤醒
                        lock.wait();
                    }
                    // retry if waited
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            // Give other threads CPU time so they hopefully drop some live references
            // and GC reclaims some space.
            // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
            // persistently throws OOME for some time...
            Thread.yield();
            // retry
            return true;
        } catch (InterruptedException x) {
            // retry
            return true;
        }

        // Fast path for cleaners
        if (c != null) {
            // 如果当前处理的reference是Cleaner类型的则执行clean方法
            c.clean();
            return true;
        }
                
              // 如果当前处理的reference不是Cleaner类型则加入到queue中
        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }


Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
 }



// 下边补充软引用与弱引用的实现
public class SoftReference<T> extends Reference<T> {

    /**
     * Timestamp clock, updated by the garbage collector
     由GC去更新时钟
     */
    static private long clock;

    /**
     * Timestamp updated by each invocation of the get method.  The VM may use
     * this field when selecting soft references to be cleared, but it is not
     * required to do so.
     维护一个时间戳,类似于lru,每次调用get方法都会更新此时间戳,JVM可以以这个时间戳为凭证,判断内存不够时选择性的回收那些不常使用的弱引用
     */
    private long timestamp;

    /**
     * Creates a new soft reference that refers to the given object.  The new
     * reference is not registered with any queue.
     *
     * @param referent object the new soft reference will refer to
     */
    public SoftReference(T referent) {
        super(referent);
        this.timestamp = clock;
    }

    /**
     * Creates a new soft reference that refers to the given object and is
     * registered with the given queue.
     *
     * @param referent object the new soft reference will refer to
     * @param q the queue with which the reference is to be registered,
     *          or <tt>null</tt> if registration is not required
     *
     */
    public SoftReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
        this.timestamp = clock;
    }

    /**
     * Returns this reference object's referent.  If this reference object has
     * been cleared, either by the program or by the garbage collector, then
     * this method returns <code>null</code>.
     *
     * @return   The object to which this reference refers, or
     *           <code>null</code> if this reference object has been cleared
     */
    public T get() {
        T o = super.get();
        if (o != null && this.timestamp != clock)
            this.timestamp = clock;
        return o;
    }

}

// 弱引用
public class WeakReference<T> extends Reference<T> {

    /**
     * Creates a new weak reference that refers to the given object.  The new
     * reference is not registered with any queue.
     *
     * @param referent object the new weak reference will refer to
     */
    public WeakReference(T referent) {
        super(referent);
    }

    /**
     * Creates a new weak reference that refers to the given object and is
     * registered with the given queue.
     *
     * @param referent object the new weak reference will refer to
     * @param q the queue with which the reference is to be registered,
     *          or <tt>null</tt> if registration is not required
     */
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }

}
  • Reference代码中包含的逻辑并非是四种引用的特性生效的全部逻辑,是需要JVM与GC配合使用的(逻辑不可见,只能从注释中瞥见一二),包括四种引用类型的子类的代码中也有对应的逻辑,大概的逻辑应该可以用下图去理解

    <img src="http://images.demoli.xyz/image-20210706201301955.png" style="zoom:200%;" />

引用分类的作用

  • 类似于Java并发编程中性能提升手段组要是锁的类型细化,引用的分类也是为了便于对引用实例进行管理以提升性能

    • 程序员自己可以手动的设置一个引用的引用类型,这使得程序员也可以使用引用介入对象生命周期的管理
    • 更有利于JVM进行垃圾回收

demoli
16 声望0 粉丝

bug创建者


« 上一篇
Go函数学习