转载

你需要知道的四种java引用

在Java语言中,除了原始数据类型(int/float等基本数据类型)的变量,其他所有的引用类型,都是指向到内存中不同的对象(一般为new操作符创建的对象)。而为了更好的管理内存,java在1.2版本中增加了包java.lang.ref,这个包提供了三个引用对象的实现类:SoftReference、WeakReference、PhantomReference。即软引用、弱引用、虚引用。此外对于直接用new操作符创建的对象,如果该对象使用一个变量来引用,那么这种引用类型称为强引用。本篇文章将从对象的生命周期、GC以及Object#finalize()方法说起,认识一下这三种引用对象的作用和应用场景。

对象的生命周期及GC

为了说明强引用、软引用、弱引用、虚引用,我们有必要来了解一下对象的生命周期和GC的基本原理。下面就以一个不十分详细的说明来描述一下。(对于想深入了解的可以参见书籍《深入理解Java虚拟书 》这本书)

假设我们有一段代码

public static void foo(String bar) {
    Integer baz = new Integer(bar);
}
// 调用函数
foo("123")

传入参数bar给方法foo(String bar),该方法内部创建了一个Integer的对象。

下图描述了在java内存空间中,栈和堆的关系。可以看出,bar作为方法foo的入参,其栈内存放了一个变量bar,指向堆中的地址,字符串”123”在堆内存中的地址。在foo函数的执行栈中,baz变量同样在栈中存放了指向堆内存,即Integer实例的内存地址。

重点:在foo函数执行完毕后,弹出执行栈,其内部的baz、bar变量立即销毁,此时堆中的”123”、Integer(“123”)对象将没有任何引用,当JVM内存不足,触发GC时,这些内存空间将被回收。

你需要知道的四种java引用

梳理一下,对于一般情况(也可以理解为强引用的情况下),堆内存中的对象,当没有变量引用时,在GC周期它们所属的内存将被回收。但在软引用、弱引用的作用下,就算堆内存中的对象被引用类型的实例(即SoftReference或WeakReference)引用,仍然可以触发回收动作。

四种引用的官方介绍

引用官方的说法 ,在GC对实例进行可达性分析时,主要有四种可达性:

  1. strongly reachable,如果某个对象可以在不遍历任何引用对象的情况下到达某个对象,则该对象是strongly reachable
  2. softly reachable,如果某个对象无法通过strongly reachable,需要通过soft reference引用对象才可达,则该对象是soft reachable
  3. weakly reachable, 如果某个对象无法通过strongly、softly reachable,而只能通过weak reference引用对象才可达,则该对象是weakly-reachable,
    当清除weakly-reachable对象时,其中包含的实例将被清除
  4. phantom reachable,无法通过strongly、softly、weakly reachable可达的,则它要么是已经被finalize机制回收或者phantom reference实例引用着

四种引用对象提供了一种有限的能力用于跟GC打交道,一个程序可以使用一个引用对象来维护某个其他对象的引用,使得后者对象仍然可以在某种情况下被收集器来回收。这样讲,可能有点不明确,请看图和描述。

你需要知道的四种java引用

在引用对象(reference-object)中使用了一个实例变量引用了一个对象(referents),在这种情况下就算引用对象(reference-object)仍然处于作用域中,被引用对象仍然有可能被GC回收。各自的作用如官网所说:

你需要知道的四种java引用

软引用的应用场景

软引用,在JVM内存不足时,将回收软引用对象中被引用的对象。

应用场景:处理超大数据集时,避免OOM

public static List<List<Object>> processResults(ResultSet rslt)
throws SQLException
{
    try
    {
        // 创建一个软引用对象,其中引用了一个LinkedList实例,用于保存处理结果
        SoftReference<List<List<Object>>> ref
        = new SoftReference<List<List<Object>>>(new LinkedList<List<Object>>());

        ResultSetMetaData meta = rslt.getMetaData();
        int colCount = meta.getColumnCount();

        int rowCount=0;
        while (rslt.next()){
            rowCount++;
            // store the row data

            List<List<Object>> results = ref.get();
            if (results == null)
                throw new TooManyResultsException(rowCount);
            else
                results.add(row);
            // 取消强引用,这样才能保证在内存不足时可以进行软引用的收集动作
            results = null;
        }

        return results;
    }
    finally
    {
        closeQuietly(rslt);
    }
}

弱引用应用场景

弱引用,当一个对象只存在弱引用时,在GC时将被回收

应用场景:

  1. 关联没有固有关系的对象
    // 固定关系的一端
    static class Person {
    
        // 当发生GC时,回收实例将调用该方法
        @Override
        protected void finalize() throws Throwable {
            System.out.println("Finalize");
            super.finalize();
        }
    }
    
private static void test1() throws InterruptedException {
    // 使用WeakHashMap来绑定两个对象的关系
    Map<Person, String> personIds = new WeakHashMap<>();
    Person person = new Person();
    // WeakHashMap会将key包装为一个WeakReference对象
    personIds.put(person, "1");

    String s = personIds.get(person);
    System.out.println(s);

    // 去掉对实例的强引用之后,当前实例只存在弱引用,即WeakHashMap中对应的key
    person = null;
    // 手动触发GC,将会回收person实例
    // 如果使用HashMap来保存关系的话,为了避免内存泄漏,我们需要手动HashMap#remove(person)
    // 来删除引用,让GC回收person。使用弱引用后,将无需我们来管理这一部分内存管理
    System.gc();

    TimeUnit.SECONDS.sleep(10);
}
  1. 通过规范化的Map来减少重复
    // 模拟String类的intern(String string)方法,来保证不会有重复对象
    private Map<String,WeakReference<String>> _map
        = new WeakHashMap<String,WeakReference<String>>();
    
    public synchronized String intern(String str)
    {
        WeakReference<String> ref = _map.get(str);
        String s2 = (ref != null) ? ref.get() : null;
        if (s2 != null)
            return s2;
    
        _map.put(str, new WeakReference(str));
        return str;
    }
    

虚引用的应用场景

虚引用(PhantomReference)不同于软引用、弱引用,其无法获得被引用对象,但是可以在引用对象被回收时,通过ReferenceQueue将自身(引用对象本身)来通知应用程序,进行一些回收资源的操作。

收到回收资源的操作,Java中有一个类似于C++的析构函数(Object#finalize)。如果在类中覆盖该方法,那么在GC回收该类的实例的时候,将触发调用该方法(GC通过一个线程来调用,时间复杂度为O(n)级别),在执行完这个方法之后,才会进行内存资源的回收。因此存在一个弊端,如果很多类都实现了finalize方法,那么会导致内存资源回收失败,发生OOM异常。

总结

  1. 四种引用:强引用、软引用、弱引用、虚引用

    2. 软引用、弱引用、虚引用在Java中各自的实现类,以及作用

    熟悉这四种引用的含义和作用,将可以帮助我们更好的了解和使用JVM内存,避免内存泄漏导致的OOM异常

参考

Java Reference Objects

官方文档

WeakReference
原文  https://blog.luhuancheng.com/2019/01/05/你需要知道的四种java引用/
正文到此结束
Loading...