Java引用类型

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 sr = new SoftReference(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 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 queue = new ReferenceQueue();
            // 虚引用必须指定一个ReferenceQueue
            PhantomReference pr = new PhantomReference(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 {
    
    // 虚引用必须结合虚引用队列使用
    private static final ReferenceQueue 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() {
              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类的解析

    Java引用类型_第1张图片

    终结器引用

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

      final class Finalizer extends FinalReference {
        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 pending = null;
      
      
      // 高优先级的引用处理线程会死循环的执行此方法
      static boolean tryHandlePending(boolean waitForNotify) {
              Reference 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 q = r.queue;
              if (q != ReferenceQueue.NULL) q.enqueue(r);
              return true;
          }
      
      
      Reference(T referent, ReferenceQueue queue) {
              this.referent = referent;
              this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
       }
      
      
      
      // 下边补充软引用与弱引用的实现
      public class SoftReference extends Reference {
      
          /**
           * 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 null if registration is not required
           *
           */
          public SoftReference(T referent, ReferenceQueue 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 null.
           *
           * @return   The object to which this reference refers, or
           *           null 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 extends Reference {
      
          /**
           * 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 null if registration is not required
           */
          public WeakReference(T referent, ReferenceQueue q) {
              super(referent, q);
          }
      
      } 
       
      • Reference代码中包含的逻辑并非是四种引用的特性生效的全部逻辑,是需要JVM与GC配合使用的(逻辑不可见,只能从注释中瞥见一二),包括四种引用类型的子类的代码中也有对应的逻辑,大概的逻辑应该可以用下图去理解

      引用分类的作用

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

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

      你可能感兴趣的:(java引用)