前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >java中的引用对象

java中的引用对象

作者头像
爬蜥
发布于 2019-07-09 12:12:58
发布于 2019-07-09 12:12:58
1.7K00
代码可运行
举报
运行总次数:0
代码可运行

参考reference 详解

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

referent表示被引用的对象。一个Reference可能有4种状态:Active、Pending、Enqueued、Inactive

在构造Reference时可以决定是否指定ReferenceQueue,会有不同的状态变更,另外一旦状态变成Inactive,状态就不会再做任何变更

ReferenceQueue 与 Reference 之间的合作

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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数据应该存活的毫秒数)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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不一致则更新

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,去掉多余的信息

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void clean() {
    if (!remove(this))
        return;
    ...
    //之前没有执行过要clean的,现在执行
    thunk.run();
    ...
}
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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实现中可以看到

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 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方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 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,那么在构造函数返回之前调用注册

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//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();
 }
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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;
}

注册执行如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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执行如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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()方法具体细节如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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);
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年03月24日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
FinalReference 如何使 GC 过程变得拖拖拉拉
提示: 为了方便大家索引,特将在上篇文章 《以 ZGC 为例,谈一谈 JVM 是如何实现 Reference 语义的》 中讨论的众多主题独立出来。
bin的技术小屋
2024/06/18
1120
FinalReference 如何使 GC 过程变得拖拖拉拉
java中的reference(三): FinalReference和Finalizer的源码分析
在前面的文章中对java 1.8中的Reference类做了详细的介绍。但是还有一个特殊的Reference并没有涉及,这就是FinalReference和其子类Finalizer。 其继承关系如下图:
冬天里的懒猫
2020/08/03
7700
深入理解JDK中的Reference原理和源码实现
这篇文章主要基于JDK11的源码和最近翻看的《深入理解Java虚拟机-2nd》一书的部分内容,对JDK11中的Reference(引用)做一些总结。值得注意的是,通过笔者对比一下JDK11和JDK8对于java.lang.ref包的相关实现,发现代码变化比较大,因此本文的源码分析可能并不适合于JDK11之外的JDK版本。
Throwable
2020/06/23
1.2K0
Java引用类型原理深度剖析,看完文章,90%的人都收藏了
Java中一共有4种引用类型(其实还有一些其他的引用类型比如FinalReference):强引用、软引用、弱引用、虚引用。其中强引用就是我们经常使用的Object a = new Object(); 这样的形式,在Java中并没有对应的Reference类。
李红
2019/08/09
3.2K0
项目中的全局缓存导致了内存泄露?
每种编程语言都有自己操作内存中元素的方式,例如在 C 和 C++ 里是通过指针,而在 Java 中则是通过“引用”。在 Java 中一切都被视为了对象,但是我们操作的标识符实际上是对象的一个引用(reference)。
架构探险之道
2020/09/18
7180
java中的reference(二): jdk1.8中Reference的源码阅读
主要的类有:Reference、SoftReference、WeakReference、PhantomReference及FinalReference、和Finalizer。其中最核心的是抽象类Reference,其他的Reference都继承了这个抽象类。分别对应java的软、弱、虚引用。而强引用是系统缺省的引用关系,用等号即可表示。因此没有专门的类。另外还有一个FinalReference,这个类主要是配合Finalizer机制使用。Finalizer本身存在诸多问题,在jdk1.9中已经被替换为另外一种Cleaner机制来配合PhantomReference机制,本文暂不涉及jdk1.9中的内容仅限于jdk1.8。 还有一个关键的类是ReferenceQueue, java.lan.ref包中各类的关系如下图:
冬天里的懒猫
2020/08/04
6720
java中的reference(二): jdk1.8中Reference的源码阅读
java强引用、软引用、弱引用、虚引用以及FinalReference
基于JDK1.8 rt.jar是java中的基础类库,在它的 java.lang.ref包下,有着一堆关于引用的类。软引用、弱引用以及虚引用的定义就在其中。另外还有一个FinalReference。
早安嵩骏
2020/08/11
1.3K0
Java中弱引用、软引用、虚引用、强引用、 Finalizer引用
在Java层面,一共有四种引用:强引用、软引用、弱引用、虚引用,这几种引用的生命周期由强到弱。转换关系大致如下图所示:
良辰美景TT
2018/12/24
2.1K0
Java中的四种Reference
首先要大致了解 Java 的几种引用类型。如下图所示,JDK 1.2 之后新增了 Reference 的概念,给开发人员提供了与 GC 交互的一种渠道。
GreatSQL社区
2023/02/23
3130
基础篇:JAVA引用类型和ThreadLocal
平时并发编程,除了维护修改共享变量的场景,有时我们也需要为每一个线程设置一个私有的变量,进行线程隔离,java提供的ThreadLocal可以帮助我们实现,而讲到ThreadLocal则不得不讲讲java的四种引用,不同的引用类型在GC时表现是不一样的,引用类型Reference有助于我们了解如何快速回收某些对象的内存或对实例的GC控制
潜行前行
2021/06/25
4050
Java 堆外内存回收原理
DirectByteBuffer 这个类是 JDK 提供使用堆外内存的一种途径,当然常见的业务开发一般不会接触到,即使涉及到也可能是框架(如 Netty、RPC 等)使用的,对框架使用者来说也是透明的。
涤生
2019/04/24
1.2K0
Java 堆外内存回收原理
设计WeakReference的那段日子
当你遇到要开发一个缓存,并且是短期内就过期的那种缓存的需求?你会怎么实现呢? Mark Reinhold看着1.1版的Java代码沉思着,最近社区传来1.1版本的一些问题,尼玛生活不容易啊。当初为了让开发者更轻松的开发代码,我们设计了垃圾回收,让开发不用管这些事情。现在可倒好,方便倒是方便了,不够灵活的问题又来了。这真是人类的终极难题啊。又要便宜又要好货!!!!! 开发者们有这样的需求,说他们要开发一个缓存组件。希望在map中的数据定期的被回收,而不至于造成内存泄露。 这在1.1中并没有这样的能力。如果
ImportSource
2018/04/03
9130
设计WeakReference的那段日子
阿里面试: 说说强引用、软引用、弱引用、虚引用吧
我们都知道 JVM 垃圾回收中,GC判断堆中的对象实例或数据是不是垃圾的方法有引用计数法和可达性算法两种。
乔戈里
2020/05/25
3.7K0
阿里面试: 说说强引用、软引用、弱引用、虚引用吧
JVM的Finalization Delay引起的OOM
今天在压力测试环境某一个服务出现crash了,经过一番检查,终于发现是由于JVM的Finalization Delay引起的,这个问题比较特殊,这里记录一下。 这个服务是用Java写的,主要完成的功能是根据特定的指令文件生成mp4文件,用到的java库主要有javacv,这个库底层其实是使用JNI调用操作系统里安装的ffmpeg。 检查日志文件 首先检查日志文件,发现日志里出现了OOM的报错 java.lang.OutOfMemoryError: null at sun.misc.Unsafe.alloc
jeremyxu
2018/05/10
1.4K0
你不可不知的 Java 引用类型之 - SoftReference 源码详解
SoftReference是软引用,其引用的对象在内存不足的时候会被回收。只有软引用指向的对象称为软可达(softly-reachable)对象。
弗兰克的猫
2018/12/28
9120
你不可不知的Java引用类型之——Reference源码解析
reference指代引用对象本身,referent指代reference引用的对象,下文介绍会以reference,referent形式出现。
弗兰克的猫
2018/11/22
1.4K0
关于Reference的与finalize的一点思考和研究
最近读了寒泉子关于Finalizer的分享 JVM源码分析之FinalReference完全解读 - InfoQ 结合之前对java引用类型的了解,突然想到几个开脑洞的问题:
左手java右手go
2019/08/20
9000
java 源码系列 - 带你读懂 Reference 和 ReferenceQueue
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/80738581
程序员徐公
2018/09/17
6840
Reference 、ReferenceQueue 详解
ReferenceQueue 引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中 实现了一个队列的入队(enqueue)和出队(poll还有remove)操作,内部元素就是泛型的Reference,并且Queue的实现,是由Reference自身的链表结构( 单向循环链表 )所实现的。 ReferenceQueue名义上是一个队列,但实际内部并非有实际的存储结构,它的存储是依赖于内部节点之间的关系来表达。可以理解为queue是一个类似于链表的结构,这里的节点其实就是refer
tomas家的小拨浪鼓
2018/06/27
1.6K0
一文讲透java弱引用以及使用场景
变量str1被用来存放一个string对象的强引用上。强引用在你正在使用时这个对象时,一般是不会被垃圾回收器回收的。当出现内存空间不足时,虚拟机不会释放强引用的对象占用的空间,而是选择抛出异常(OOM)。
用户7634691
2022/05/31
1.8K0
一文讲透java弱引用以及使用场景
推荐阅读
相关推荐
FinalReference 如何使 GC 过程变得拖拖拉拉
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文