Java虚拟机(JVM)面试专题 下(初级程序员P6)

Java虚拟机(JVM)面试专题 下(初级程序员P6)

六、四种引用

1. 强引用

2. 软引用(SoftReference)

3. 弱引用(WeakReference)

软引用和弱引用的回收时机对比

4. 虚引用(PhantomReference)

【BUG-ThreadLoadMap】弱引用的问题

为什么“b”不会被清除?

小结

七、finalize

什么是finalize方法?

finalize 原理

finalize 缺点【非常不好】

非常不好

影响性能


Java虚拟机(JVM)面试专题 下(初级程序员P6)

六、四种引用

1. 强引用

普通变量赋值即为强引用,如 A a = new A();

通过 GC Root 的引用链,如果强引用不到该对象,该对象才能被回收

当之后内存不足的时候,JVM就会发生垃圾回收,如果通过GC Root引用链找到的对象(强引用关系)就不能被回收! 

2. 软引用(SoftReference)

例如:SoftReference a = new SoftReference(new A());

如果仅有软引用该对象时,首次垃圾回收不会回收该对象;如果内存仍不足,再次回收时才会释放对象(只是a对象,并不是软引用本身) 

软引用自身需要配合引用队列来释放

典型例子是反射数据!我们可以通过反射获取类变量,然后再通过类变量获取成员变量、方法信息,这些数据都是软引用数据

3. 弱引用(WeakReference)

例如:WeakReference a = new WeakReference(new A());

如果仅有弱引用引用该对象时,只要发生垃圾回收,就会释放该对象

弱引用自身需要配合引用队列来释放

典型例子是 ThreadLocalMap 中的 Entry 对象!内存不足的时候,它会把 Entry对象的key删除

软引用和弱引用的回收时机对比

软引用首次GC是不会回收该对象,再次回收才会

弱引用只要发生垃圾回收,就会释放该对象

public class SoftReferenceDemo {
    public static void main(String[] args) {
        // 强引用
        String str = new String("abc");
        // 软引用
        SoftReference<String> softRef = new SoftReference<String>(str);
        str = null;  // 去掉强引用
        System.gc(); // 垃圾回收器进行回收
        System.out.println(softRef.get());//abc

        // 强引用
        String abc = new String("123");
        // 弱引用
        WeakReference<String> weakRef = new WeakReference<String>(abc);
        abc = null;  // 去掉强引用
        System.gc(); // 垃圾回收器进行回收
        System.out.println(weakRef.get());//null
    }
}

运行结果 

4. 虚引用(PhantomReference)

例如: PhantomReference a = new PhantomReference(new A(), referenceQueue);

必须配合引用队列一起使用,当虚引用所引用的对象被回收时,由 Reference Handler 线程将虚引用对象入队,这样就可以知道哪些对象被回收,从而对它们关联的资源做进一步处理。

 也就是说,当a对象、b对象被垃圾回收了,虚引用对象A、B就会被放到引用队列里面,由Reference Handler 线程释放该对象和其关联的外部资源!

【BUG-ThreadLoadMap】弱引用的问题

ThreadLoadMap有点特殊,如果我们使用不当,就可能会造成内存泄漏,它里面的key是弱引用,而value是强引用

我们这里可以采用引用队列的思路来释放,看下面代码

public class WeakReferenceDemo {

    public static void main(String[] args) {
        MyWeakMap map = new MyWeakMap();
        map.put(0, new String("a"), "1");
        map.put(1, "b", "2"); // 
        map.put(2, new String("c"), "3");
        map.put(3, new String("d"), "4");
        System.out.println(map);

        // Entry是强引用,new String("a")是弱引用!
        System.gc();
        // 垃圾回收后,只有b在!
        System.out.println("a:" + map.get("a"));
        System.out.println("b:" + map.get("b"));
        System.out.println("c:" + map.get("c"));
        System.out.println("d:" + map.get("d"));
        System.out.println(map);
        map.clean();
        System.out.println(map);
    }

    // 模拟 ThreadLocalMap 的内存泄漏问题以及一种解决方法
    static class MyWeakMap {

        // 引用队列
        static ReferenceQueue<Object> queue = new ReferenceQueue<>();
        // 这个Entry和ThreadLocalMap类似,key是弱引用,value是强引用!
        static class Entry extends WeakReference<String> {
            String value;

            public Entry(String key, String value) {
                // 加入引用队列
                super(key, queue); //弱引用
                this.value = value;
            }
        }

        // 清除Entry中元素占用内存
        public void clean() {
            Object ref;
            // 从队列里面取
            while ((ref = queue.poll()) != null) {
                System.out.println(ref);
                for (int i = 0; i < table.length; i++) {
                    if(table[i] == ref) {
                        table[i] = null;
                    }
                }
            }
        }

        Entry[] table = new Entry[4];

        public void put(int index, String key, String value) {
            table[index] = new Entry(key, value);
        }

        // 遍历数组,获取值
        public String get(String key) {
            for (Entry entry : table) {
                if (entry != null) {
                    String k = entry.get();
                    if (k != null && k.equals(key)) {
                        return entry.value;
                    }
                }
            }
            return null;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("[");
            for (Entry entry : table) {
                if (entry != null) {
                    String k = entry.get();
                    sb.append(k).append(":").append(entry.value).append(",");
                }
            }
            if (sb.length() > 1) {
                sb.deleteCharAt(sb.length() - 1);
            }
            sb.append("]");
            return sb.toString();
        }
    }
}

运行结果 

为什么“b”不会被清除?

map.put(0, new String("a"), "1");
map.put(1, "b", "2"); 

使用new String(" ")创建的对象,在里面,当没有别的地方引用它的时候,垃圾回收时,它就会被清除掉!

如果不用new,它就会在字符串常量池中存储一份,这个引用时强引用,所以不会被垃圾回收清除!

小结

前面的弱引用、虚引用可以配合引用队列,实现对垃圾的清理,它们的目的其实都是差不多的,都是为了找到那些被清除的Java对象,然后再释放它们所关联的外部资源! 

另外,在JDK9中,提供了Cleaner类,用于清理相关资源!

Java之Cleaner - 时间完全不够用啊 - 博客园 (cnblogs.com)https://www.cnblogs.com/0099-ymsml/p/15861500.html

七、finalize

什么是finalize方法?

它是 Object 中的一个方法,如果子类重写它垃圾回收时此方法会被调用,可以在其中进行资源释放和清理工作

将资源释放和清理放在 finalize 方法中非常不好,非常影响性能,严重时甚至会引起 OOM,从 Java9 开始就被标注为 @Deprecated,不建议被使用了(建议使用上面讲的Cleaner!)

finalize 原理

(1)对 finalize 方法进行处理的核心逻辑位于 java.lang.ref.Finalizer 类中,它包含了名为 unfinalized 的静态变量(双向链表结构),Finalizer 也可被视为另一种引用对象(地位与软、弱、虚相当,只是不对外,无法直接使用)

(2)当重写了 finalize 方法的对象,在构造方法调用之时,JVM 都会将其包装成一个 Finalizer 对象,并加入 unfinalized 链表

(3)Finalizer 类中还有另一个重要的静态变量,即 ReferenceQueue 引用队列,刚开始它是空的。当狗对象可以被当作垃圾回收时,就会把这些狗对象对应的 Finalizer 对象加入此引用队列

(4)但此时 Dog 对象还没法被立刻回收,因为 unfinalized -> Finalizer 这一引用链还在引用它嘛,为的是【先别着急回收啊,等我调完 finalize 方法,再回收】

(5)FinalizerThread 线程会从 ReferenceQueue 中逐一取出每个 Finalizer 对象,把它们从链表断开并真正调用 finallize 方法

(6)由于整个 Finalizer 对象已经从 unfinalized 链表中断开,这样没谁能引用到它和狗对象,所以下次 gc 时就被回收了

finalize 缺点【非常不好】

非常不好

  • 无法保证资源释放:FinalizerThread 是守护线程,代码很有可能没来得及执行完,线程就结束了

  • 无法判断是否发生错误:执行 finalize 方法时,会吞掉任意异常,有的时候甚至不能判断有没有在释放资源的时候发生错误

影响性能

  • 内存释放不及时:重写了 finalize 方法的对象在第一次被 gc 时,并不能及时释放它占用的内存,因为要等着 FinalizerThread 调用完 finalize,把它从 unfinalized 队列移除后,第二次 gc 时才能真正释放内存

  • gc本来就是内存不足所引发的,但是调动finalize又非常慢!要等待,所以非常影响性能!

posted @ 2023-01-03 20:28  金鳞踏雨  阅读(13)  评论(0编辑  收藏  举报  来源