zoukankan      html  css  js  c++  java
  • Java中的四种引用

    Java中存在四种引用,StrongReference(强引用) 、SoftReferenc(软引用) 、WeakReferenc(弱引用)、PhantomReference(虚引用).虽然不常用,但是对于理解Java的回收等级还是很有帮助的,一句话来说这些引用只是不同回收等级的一种表现形式.
    图片


    StrongReference(强引用)

    强引用是最经常使用的一种引用,如new操作创建的对象就属于强引用.如下代码,对于强引用要记住无论如何JVM都不会去回收其内存.
    清单1:强引用示例

    1
    Object obj = new Object();

    SoftReferenc(软引用)

    软引用是由java.lang.ref.SoftReference所提供的功能,被其所关联的对象不存在强引用并且此时JVM内存不足才会去回收该对象.
    个人不知道其用处,做缓存的话,现在的企业项目基本不是单体架构所以用处不大,倒是可以做内存警告,当对象被回收时则说明系统所需要的内存不足,那么就可以发邮件通知相关人员.

    WeakReferenc(弱引用)

    弱引用是java.lang.ref包下的WeakReferenc类所提供的包装功能,对于弱引用JVM会回收仅被弱引用所关联的对象.也就是说弱引用对象会在一次gc之后被回收,如下代码,其中obj1没被回收,因为其的引用是强引用,但是weakObj1与其关联是弱引用,因此不属于被收回对象.weakObj2所关联的new Object()只有一个弱引用关联,因此会被回收.

    清单2:弱引用示例

    1
    2
    3
    4
    5
    6
    7
    8
    Object obj1 = new Object();
    WeakReference<Object> weakObj1 = new WeakReference<Object>(obj1);
    WeakReference<Object> weakObj2 = new WeakReference<Object>(new Object());
    //主动回收
    System.gc();

    System.out.println(weakObj1.get()); // 非null
    System.out.println(weakObj2.get()); // null

    Java中提供了一个很棒的工具类WeakHashMap,按照注释所说,该类是一个键为弱引用类型的Map,与传统Map不同的是其键会自动删除释放掉,因为gc()时会自动释放,因此很适合做缓存这一类的需求,下面代码是Tomcat所实现的LRU(最少使用策略)缓存算法的实现,关键点在注释中给出.
    清单3:弱引用实现LRU

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    import java.util.Map;
    import java.util.WeakHashMap;
    import java.util.concurrent.ConcurrentHashMap;

    public final class ConcurrentCache<K,V> {
    //LRU所允许的最大缓存量
    private final int size;
    private final Map<K,V> eden;
    private final Map<K,V> longterm;

    public ConcurrentCache(int size) {
    this.size = size;
    //eden是主要缓存
    this.eden = new ConcurrentHashMap<>(size);
    //longterm是实现LRU算法的关键点.
    this.longterm = new WeakHashMap<>(size);
    }

    //get是先从eden中取出缓存,当不存在时则去longterm中获取缓存,并且此时获取到的缓存说明还在使用,因此会put到eden中(LRU算法)
    public V get(K k) {
    V v = this.eden.get(k);
    if (v == null) {
    synchronized (longterm) {
    v = this.longterm.get(k);
    }
    if (v != null) {
    this.eden.put(k, v);
    }
    }
    return v;
    }
    //put操作当size大于LRU最大容量时,则把缓存都放入到longterm,当this.eden.clear()后使其成为弱引用,那么LRU的实现则在get方法中体现了出来.
    public void put(K k, V v) {
    if (this.eden.size() >= size) {
    synchronized (longterm) {
    this.longterm.putAll(this.eden);
    }
    this.eden.clear();
    }
    this.eden.put(k, v);
    }
    }

    此方法如果操作时刚好遇到了一次gc,那么longterm的引用就会丢失,那么缓存就gg了.

    PhantomReference(虚引用)

    虚引用是由java.lang.ref.PhantomReference所提供的关联功能,虚引用对其原对象的生命周期毫无影响,其可以算是一种标记,当其所引用对象被回收时其会自动加入到引用队列中.也就是说你可以通过虚引用得到哪些对象已被回收.具体用法可以分析common.io中的org.apache.commons.io.FileCleaningTracker
    该类中有一内部类class Tracker extends PhantomReference<Object>,也就是其包裹着虚引用对象,分析其构造函数,marker参数是该具体的虚引用,当marker被回收时,该对应的Track会被加入到引用队列queue中.
    清单4:虚引用示例

    1
    2
    3
    4
    5
    6
    Tracker(String path, FileDeleteStrategy deleteStrategy, Object marker, ReferenceQueue<? super Object> queue) {
    //marker是具体的虚引用对象
    super(marker, queue);
    this.path = path;
    this.deleteStrategy = deleteStrategy == null ? FileDeleteStrategy.NORMAL : deleteStrategy;
    }

    文件删除则是该类维护的一个线程来进行的操作,既然对象回收后会加入到引用队列queue,那么该线程要做的功能自然是从引用队列中获取到对应的Track,然后执行其删除策略.
    在这个流程中虚引用起到的是跟踪所包裹对象作用,当包裹的的对象被回收时,这边会得到一个通知(将其加入到引用队列).
    清单5:虚引用回调

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Override
    public void run() {
    // thread exits when exitWhenFinished is true and there are no more tracked objects
    while (exitWhenFinished == false || trackers.size() > 0) {
    try {
    // Wait for a tracker to remove.
    Tracker tracker = (Tracker) q.remove(); // cannot return null
    trackers.remove(tracker);
    if (!tracker.delete()) {
    deleteFailures.add(tracker.getPath());
    }
    tracker.clear();
    } catch (InterruptedException e) {
    continue;
    }
    }
    }

    ReferenceQueue

    在弱引用以及虚引用示例中都涉及到ReferenceQueue的回调机制,ReferenceQueue是引用队列,是GC和应用系统交互的一种方式,当对象被回收时GC会将回收信息加入到ReferenceQueue中,应用系统能够拿到回收信息,进而做资源释放等处理,比如WeakHashMap,那具体是怎么实现的呢?
    WeakHashMap构造过程中每一个K-V会被封装成java.util.WeakHashMap.Entry对象,该对象继承了WeakReference,从构造函数来看每一个Key实际上为WeakReference包裹的对象。
    清单6:WeakHashMap的Entry构造函数

    1
    2
    3
    4
    5
    6
    7
    8
    Entry(Object key, V value,
    ReferenceQueue<Object> queue,
    int hash, Entry<K,V> next) {
    super(key, queue); // 把Key传入WeakReference的构造函数中
    this.value = value;
    this.hash = hash;
    this.next = next;
    }

    从构造函数可以得出Key是弱引用,而Value并不是,因此当Key被回收时,WeakHashMap要做对应的Value清理工作。具体清理逻辑在expungeStaleEntries方法中,再调用size(),get()等方法时会显示触发该方法,该方法会把对应被回收key所在的Entry从链中释放掉,如清单7所示。
    清单7:WeakHashMap对象清理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    private void expungeStaleEntries() {
    for (Object x; (x = queue.poll()) != null; ) {
    synchronized (queue) { // 每一次回收都会加锁
    @SuppressWarnings("unchecked")
    Entry<K,V> e = (Entry<K,V>) x; //被回收的key
    int i = indexFor(e.hash, table.length); // 找到对应的hash槽

    Entry<K,V> prev = table[i];
    Entry<K,V> p = prev;
    while (p != null) { // 遍历hash槽对应的链,找到该entry之后修改链表
    Entry<K,V> next = p.next;
    if (p == e) {
    if (prev == e)
    table[i] = next;
    else
    prev.next = next;
    // Must not null out e.next;
    // stale entries may be in use by a HashIterator
    e.value = null; // Help GC
    size--;
    break;
    }
    prev = p;
    p = next;
    }
    }
    }
    }

    那么又会有新的问题了,被回收的对象是怎么加入到ReferenceQueue中的?这里要从Reference类开始看起。
    无论是弱引用,软引用,虚引用都会继承java.lang.ref.Reference对象,该对象从数据结构角度看是一个单链表,其内部持有引用队列对象,以及被回收的对象链表,这里很多字段在代码层面都没有直接调用,而是JVM内部会去调用该字段直接赋值。

    清单8:Reference部分结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public abstract class Reference<T> {

    private T referent; /* Treated specially by GC */

    // 引用所关联的引用队列,构造函数传入
    volatile ReferenceQueue<? super T> queue;

    // 单链表
    @SuppressWarnings("rawtypes")
    volatile Reference next;

    transient private Reference<T> discovered; /* used by VM */

    // 被回收的对象列表,注意是static,全局共享
    private static Reference<Object> pending = null;
    }

    有了这些信息,Reference会在类加载阶段创建一个守护线程ReferenceHandler,通过Debug可以很容易发现该线程,该线程会去扫描被回收对象链表,然后把被回收的对象加入到ReferenceQueue中,这样完成了整个通知流程。
    图片

  • 相关阅读:
    MySQL日志
    MySQL备份与恢复
    MySQL创建数据表并建立主外键关系
    MySQL函数的使用
    MySQL存储过程
    MySQL变量的使用
    MySQL个人学习笔记
    SQL Server CLR 使用 C# 自定义存储过程和触发器
    SQL Server CLR 使用 C# 自定义函数
    LC 918. Maximum Sum Circular Subarray
  • 原文地址:https://www.cnblogs.com/lanblogs/p/15161780.html
Copyright © 2011-2022 走看看