java中的引用对象

921 阅读6分钟

参考reference 详解

java中使用Reference对象来描述所有的引用对象

referent表示被引用的对象。一个Reference可能有4种状态:Active、Pending、Enqueued、Inactive
在构造Reference时可以决定是否指定ReferenceQueue,会有不同的状态变更,另外一旦状态变成Inactive,状态就不会再做任何变更

ReferenceQueue 与 Reference 之间的合作

当GC发生时,被回收的对象会添加到Pending列表中,通过Reference的next字段来构建Pending链表。在Reference的静态代码块,则会启动RefenrenceHandler

static {
    ...
 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();
    ...
}

并设置为最大的优先级,它负责将pending中的元素添加到ReferenceQueue,

private static class ReferenceHandler extends Thread {
     public void run() {
           for (;;) {
               Reference r;
               synchronized (lock) {
                   if (pending != null) {
                       r = pending;
                       Reference rn = r.next;
                       pending = (rn == r) ? null : rn;
                       r.next = r;
                   } else {
                       try {
                           lock.wait();
                       } catch (InterruptedException x) { }
                       continue;
                   }
               }
               // Fast path for cleaners
               if (r instanceof Cleaner) {
                   ((Cleaner)r).clean();
                   continue;
               }
               //存在ReferenceQueue时,将pending中的元素入队列
               ReferenceQueue q = r.queue;
               if (q != ReferenceQueue.NULL) q.enqueue(r);
           }
       }
   }

ReferenceQueue提供对列的功能,出队和入队,当ReferenceQueue作为参数被提供时,这意味着用户一旦从ReferenceQueue中获取到元素,也就可以知道,这个对象要被回收了,以此达到一种通知的效果

强引用、软引用、弱引用与虚引用

  • 强引用。比如通过 new 生成的对象,这类可确保不会被GC回收掉
  • 软引用。一旦内存即将溢出,就把这类对象都回收掉,适用于内存敏感的缓存使用
  • 弱引用。每次垃圾回收都可以回收这些引用对象
  • 虚引用。与对象的生存无关,仅提供通知机制

    虚引用一定要提供ReferenceQueue,因为它无法返回引用为null,如果不提供,那么连通知的机制都无法实现了

软引用回收策略细节

软引用不仅考虑内存,还会考虑referent的使用情况和创建时间来决定是否该回收。Hotspot会读取当前堆剩余的内存,以及配置参数XX:SoftRefLRUPolicyMSPerMB(每M数据应该存活的毫秒数)

void LRUMaxHeapPolicy::setup() {
  size_t max_heap = MaxHeapSize;
  max_heap -= Universe::get_heap_used_at_last_gc();
  max_heap /= M;
  //剩余空间能够存的以M为单位的数据应该存活的时间
  _max_interval = max_heap * SoftRefLRUPolicyMSPerMB;
  assert(_max_interval >= 0,"Sanity check");
}

// The oop passed in is the SoftReference object, and not
// the object the SoftReference points to.
bool LRUCurrentHeapPolicy::should_clear_reference(oop p,
                                                  jlong timestamp_clock) {
  jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);
  assert(interval >= 0, "Sanity check");

  // The interval will be zero if the ref was accessed since the last scavenge/gc.
 
  if(interval <= _max_interval) {
    return false;
  }

  return true;
}

软引用自身携带timestamp和clock,其中clock由GC更新,timestamp每次get的时候,如果和clock不一致则更新

public T get() {
    T o = super.get();
    if (o != null && this.timestamp != clock)
        this.timestamp = clock;
    return o;
}

如果再上一次GC之后,有过访问记录,那么当前的GC肯定不会回收软引用,这也就意味着,软引用如果一直没有回收,升级到老年代,在OOM之前,有可能出现频繁的Full GC

WeakHashMap 对弱引用的使用

weakHashMap在 get/put/remove/resize等方法中均使用了expungeStaleEntries,去掉多余的信息

private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
...
 private void expungeStaleEntries() {
    //从注册的队列中拿到了值
    for (Object x; (x = queue.poll()) != null; ) {
        synchronized (queue) {
            Entry<K,V> e = (Entry<K,V>) x;
          ...
          if (p == e) {
             e.value = null; // Help GC
             size--;
              ...
            }
        }
    }
}

如果从注册的队列中拿到了对应的元素,那么就自动删掉,这里就是利用了ReferenceQueue承担通知的角色,以及弱引用的GC就回收性质

Cleaner与native内存回收

在ReferenceHandler中注意到,如果pending中的Reference是一个Cleaner,则直接执行clean

public void clean() {
    if (!remove(this))
        return;
    ...
    //之前没有执行过要clean的,现在执行
    thunk.run();
    ...
}
}

以DirectByteBuffer为例,在使用时,就会创建一个Cleaner

DirectByteBuffer(int cap) { 
    ...
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
}
private static class Deallocator
    implements Runnable
{
    public void run() {
        if (address == 0) {
            // Paranoia
            return;
        }
        unsafe.freeMemory(address);
        address = 0;
        Bits.unreserveMemory(size, capacity);
    }
}

java中通过ByteBuffer.allocateDirect就可以创建,如果DirectByteBuffer被回收,此时唯一引用DirectByteBuffer的是一个虚引用,由于垃圾回收的作用,DirectByteBuffer会处于pending状态,触发Native内存的回收释放

参考直接内存

延伸一点网络读写过程非直接内存转换成直接内存的行为,javaNio中写数据IOUtil.write实现中可以看到

 static long write(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length,
                      NativeDispatcher nd){
     ...
      if (!(buf instanceof DirectBuffer)) {
         //分配直接内存
         ByteBuffer shadow = Util.getTemporaryDirectBuffer(rem);
         shadow.put(buf);
         shadow.flip();
         vec.setShadow(iov_len, shadow);
         buf.position(pos);  // temporarily restore position in user buffer
         buf = shadow;
         pos = shadow.position();
     }
     ...
     
 }

会发现如果要将一个byte数组对象传给native,会先转换成直接内存再操作,这是因为native代码访问数组必须保证访问的时候,byte[]对象不能移动,也就是被"pin"钉住,此时要么是暂停GC(GC算法有可能要移动对象),要么是假设换成native的消耗是可接受的,而且I/O操作都很慢,这里就选择了后者

Finalizer

Finalizer自身会启动一个线程,它自己的工作就是一直从ReferenceQueue中拉取对应的元素并执行它的runFinalizer方法

 private static class FinalizerThread extends Thread {
        FinalizerThread(ThreadGroup g) {
            super(g, "Finalizer");
        }
        public void run() {
            for (;;) {
                try {
                    Finalizer f = (Finalizer)queue.remove();
                    f.runFinalizer();
                } catch (InterruptedException x) {
                    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();
    }

值得注意的是,Finalizer它本身的构造函数是private,只能通过虚拟机自身来执行register操作,具体的时机根据RegisterFinalizersAtInit参数来决定,如果值为true,那么在构造函数返回之前调用注册

//vmSymbols.hpp
...
template(object_initializer_name,                   "<init>") 
...
do_intrinsic(_Object_init,              java_lang_Object, object_initializer_name, void_method_signature,        F_R)   \

//c1_GraphBuilder.cpp
void GraphBuilder::method_return(Value x) {
//RegisterFinalizersAtInit为true
  if (RegisterFinalizersAtInit &&
      method()->intrinsic_id() == vmIntrinsics::_Object_init) {
    call_register_finalizer();
 }

否则在分配好空间对象之后,再注册

instanceOop instanceKlass::allocate_instance(TRAPS) {
  assert(!oop_is_instanceMirror(), "wrong allocation path");
  bool has_finalizer_flag = has_finalizer(); // Query before possible GC
  int size = size_helper();  // Query before forming handle.

  KlassHandle h_k(THREAD, as_klassOop());

  instanceOop i;

  i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL);
  if (has_finalizer_flag && !RegisterFinalizersAtInit) {
   //RegisterFinalizersAtInit为false
    i = register_finalizer(i, CHECK_NULL);
  }
  return i;
}

注册执行如下

instanceOop instanceKlass::register_finalizer(instanceOop i, TRAPS) {
  if (TraceFinalizerRegistration) {
    tty->print("Registered ");
    i->print_value_on(tty);
    tty->print_cr(" (" INTPTR_FORMAT ") as finalizable", (address)i);
  }
  instanceHandle h_i(THREAD, i);
  // Pass the handle as argument, JavaCalls::call expects oop as jobjects
  JavaValue result(T_VOID);
  JavaCallArguments args(h_i);
  //finalizer_register_method即通过Finalizer找到的register
  methodHandle mh (THREAD, Universe::finalizer_register_method());
  JavaCalls::call(&result, mh, &args, CHECK_NULL);
  return h_i();
}

runFinalizer执行如下

private void runFinalizer() {
    synchronized (this) {
        if (hasBeenFinalized()) return;
        remove();
    }
    try {
        Object finalizee = this.get();
        if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
            invokeFinalizeMethod(finalizee);
            /* Clear stack slot containing this variable, to decrease
               the chances of false retention with a conservative GC */
            finalizee = null;
        }
    } catch (Throwable x) { }
    super.clear();
}

则是先看是不是已经执行过,执行过就返回,这里可以得到如下三点信息

  1. 对象的finalize()方法只会执行一次
  2. 如果在第一次执行finalize的时候让对象强行恢复引用,则可以逃过第一次的GC,但是由于第二次不会再执行,此时则会被回收掉
  3. 对于Finalizer对象本身,由于它存在内部的unfinalized对象构建的强引用,第一次GC执行,只是在等待runFinalizer的执行,如果执行了,并且之前没有执行过才会从 unfinalized列表中进行删掉,从而不可达,再第二次GC的时候回收了Finalizer本身

执行finalize()方法具体细节如下


JNIEXPORT void JNICALL
Java_java_lang_ref_Finalizer_invokeFinalizeMethod(JNIEnv *env, jclass clazz,
                                                  jobject ob)
{
    jclass cls;
    jmethodID mid;

    cls = (*env)->GetObjectClass(env, ob);
    if (cls == NULL) return;
    mid = (*env)->GetMethodID(env, cls, "finalize", "()V");
    //没有finalize什么都不做
    if (mid == NULL) return;
    //执行
    (*env)->CallVoidMethod(env, ob, mid);
}