Java之道系列:WeakHashMap实现浅析

举个栗子

关于Reference对象,java.lang.ref包的文档说了一堆,跑起来看看才是王道,

public class Foo {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize#" + this);
        super.finalize();
    }
}
    private static ReferenceQueue<Foo> queue = new ReferenceQueue<Foo>();

    static {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    Reference ref = null;
                    try {
                        ref = queue.remove();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("removed#" + ref);
                }
            }
        }).start();
    }

    public static void main(String[] args) throws Exception{

        PhantomReference<Foo> pr = pr(); // phantom reachable
        WeakReference<Foo> wr = wr();    // weakly reachable
        SoftReference<Foo> sr = sr();    // softly reachable

        System.gc();
        Thread.sleep(2000);

        System.out.println("pr.isEnqueued()#"+pr.isEnqueued());
        System.out.println("wr.get()#"+wr.get());
        System.out.println("sr.get()#"+sr.get());

    }

    private static PhantomReference<Foo> pr() {
        Foo foo = new Foo();
        PhantomReference<Foo> pr = new PhantomReference<Foo>(foo, queue);
        return pr;
    }

    private static WeakReference<Foo> wr() {
        Foo foo = new Foo();
        WeakReference<Foo> wr = new WeakReference<Foo>(foo, queue);
        return wr;
    }

    private static SoftReference<Foo> sr() {
        Foo foo = new Foo();
        SoftReference<Foo> sr = new SoftReference<Foo>(foo, queue);
        return sr;
    }

输出是这样的,

removed#java.lang.ref.WeakReference@d48785
finalize#[email protected]
finalize#me.kisimple.just4fun.Foo@fdd15b
pr.isEnqueued()#false
wr.get()#null
sr.get()#me.kisimple.just4fun.Foo@a0fbd6

有时候是这样的,

finalize#[email protected]
finalize#[email protected]
removed#[email protected]
pr.isEnqueued()#false
wr.get()#null
sr.get()#[email protected]

finalize#removed#的顺序并不固定。

下面我们就根据这个输出来分析下当

the reachability of the referent has changed to the value corresponding to the type of the reference

时,也就是注释中的xxx reachable,referent的回收和reference入队列这两个操作。

finalize

  • SoftReference,并不会被回收。SoftReference会尽可能长时间的保存在内存当中,只要不会OOM:)
  • WeakReference,会被回收,finalize已执行;
  • PhantomReference,会被回收,finalize已执行;

enqueue

  • SoftReference,并没有入队列;
  • WeakReference,会入队列,入队列的时机与finalize的执行顺序并不固定;
  • PhantomReference,也没有入队列;

但是API文档中说道,

Some time after the garbage collector determines that the reachability of the referent has changed to the value corresponding to the type of the reference, it will add the reference to the associated queue.

但从上面的栗子只看到WeakReference的行为符合文档的说明,是有bug还是栗子有问题?

内存泄漏

关于WeakHashMap,API文档说明如下,

More precisely, the presence of a mapping for a given key will not prevent the key from being discarded by the garbage collector, that is, made finalizable, finalized, and then reclaimed. When a key has been discarded its entry is effectively removed from the map, so this class behaves somewhat differently from other Map implementations.

也就是说,即使WeakHashMap持有了key对象也不会阻止这个key对象被回收,也就相当于说,WeakHashMap只是持有了该key对象的一个WeakReference而已。当key对象被回收之后,相应的mapping也会被回收。来看下面的栗子,

    private static class Foo {
        @Override
        protected void finalize() throws Throwable {
            System.out.println("finalize#" + this);
            super.finalize();
        }
    }

    private static class Bar {
        private long id;
        public Bar(long id) {
            this.id = id;
        }
        @Override
        public boolean equals(Object obj) {
            return obj instanceof Bar ?
                    ((Bar)obj).id == this.id : false;
        }
        @Override
        public int hashCode() {
            return (int) (id ^ (id >>> 32));
        }
    }

    private static Map<Bar, Object> sm = new HashMap<Bar, Object>();
    private static Map<Bar, Object> wm = new WeakHashMap<Bar, Object>();

    public static void main(String[] args) throws Exception{

        strongMap();
        weakMap();

        System.gc();
        Thread.sleep(2000);

        System.out.println(sm.get(new Bar(27)));
        System.out.println(wm.get(new Bar(127)));

    }

    private static void strongMap() {
        Foo foo = new Foo();
        sm.put(new Bar(27), foo);
    }
    private static void weakMap() {
        Foo foo = new Foo();
        wm.put(new Bar(127), foo);
    }

输出如下,WeakHashMap持有的mapping妥妥的被回收了,而普通的HashMap则不会。

me.kisimple.just4fun.Main$Foo@2dce0
null

依靠这个特性WeakHashMap可以代替HashMap来防止一些内存泄漏,具体可参考这篇文档

WeakHashMap

那么WeakHashMap到底是怎么实现的?RTFSC

    /**
     * The entries in this hash table extend WeakReference, using its main ref
     * field as the key.
     */
    private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        int hash;
        Entry<K,V> next;

        /**
         * Creates new entry.
         */
        Entry(Object key, V value,
              ReferenceQueue<Object> queue,
              int hash, Entry<K,V> next) {
            super(key, queue); // 也就不需要有this.key = key;这么一行了
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }

        @SuppressWarnings("unchecked")
        public K getKey() {
            return (K) WeakHashMap.unmaskNull(get());
        }

        public V getValue() {
            return value;
        }

        public V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public boolean equals(Object o) {...}

        public int hashCode() {...}

        public String toString() {...}
    }

WeakHashMap与普通Map最大的区别就在于这个Entry,它继承了WeakReference,并且在它的构造函数中有这么一句,super(key, queue);,所以正如上面我们所说的,WeakHashMap只持有了一个key对象的弱引用,后面只要去查询引用队列queue就可以知道这个key对象是不是已经被回收,如果被回收就将该Entry也清除掉,相应代码在expungeStaleEntries方法中,

    /**
     * Expunges stale entries from the table.
     */
    private void expungeStaleEntries() {
        for (Object x; (x = queue.poll()) != null; ) {
            synchronized (queue) {
                @SuppressWarnings("unchecked")
                    Entry<K,V> e = (Entry<K,V>) x;
                int i = indexFor(e.hash, table.length);

                Entry<K,V> prev = table[i];
                Entry<K,V> p = prev;
                while (p != null) {
                    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;
                }
            }
        }
    }

大部分对WeakHashMap的操作都会先去调用getTable方法,而该方法会先去执行expungeStaleEntries方法来执行上述的清理。

    /**
     * Returns the table after first expunging stale entries.
     */
    private Entry<K,V>[] getTable() {
        expungeStaleEntries();
        return table;
    }

这样也就不需要去启动一个线程专门来轮询引用队列了。

参考资料

时间: 2024-11-23 17:26:38

Java之道系列:WeakHashMap实现浅析的相关文章

Java之道系列:BigDecimal如何解决浮点数精度问题

如题,今天我们来看下java.math.BigDecimal是如何解决浮点数的精度问题的,在那之前当然得先了解下浮点数精度问题是什么问题了.下面我们先从IEEE 754说起. IEEE 754 IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用.这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denormal number)),一些特殊数值(无穷(Inf)与非数值(NaN)),以及这些数值的"浮点数运算符&qu

Java之道系列:Annotation实现机制

今天来研究研究Java的注解是如何实现的.最简单的,先来看看注解的class文件是什么样的. class文件支持 直接看栗子, import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHO

深入Java集合学习系列:HashMap的实现原理

参考文献 引用文献:深入Java集合学习系列:HashMap的实现原理,大部分参考这篇博客,只对其中进行稍微修改 自己曾经写过的:Hashmap实现原理 1. HashMap概述: HashMap是基于哈希表的Map接口的非同步实现(Hashtable跟HashMap很像,唯一的区别是Hashtalbe中的方法是线程安全的,也就是同步的).此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映射的顺序,特别是它不保证该顺序恒久不变. 2. HashMap的数据结构: 在ja

推荐:Java性能优化系列集锦

Java性能问题一直困扰着广大程序员,由于平台复杂性,要定位问题,找出其根源确实很难.随着10多年Java平台的改进以及新出现的多核多处理器,Java软件的性能和扩展性已经今非昔比了.现代JVM持续演进,内建了更为成熟的优化技术.运行时技术和垃圾收集器.与此同时,底层的硬件平台和操作系统也在演化. 目录: 一.Java性能优化系列之一--设计优化 二.Java性能优化系列之二--程序优化 三.Java性能优化系列之三--并发程序设计详解 四.Java性能优化系列之四--Java内存管理与垃圾回收

Java总结篇系列:java.lang.Object

从本篇开始,将对Java中各知识点进行一次具体总结,以便对以往的Java知识进行一次回顾,同时在总结的过程中加深对Java的理解. Java作为一个庞大的知识体系,涉及到的知识点繁多,本文将从Java中最基本的类java.lang.Object开始谈起. Object类是Java中其他所有类的祖先,没有Object类Java面向对象无从谈起.作为其他所有类的基类,Object具有哪些属性和行为, 是Java语言设计背后的思维体现. Object类位于java.lang包中,java.lang包包

Java Web学习系列——Maven Web项目中集成使用Spring、MyBatis实现对MySQL的数据访问

本篇内容还是建立在上一篇Java Web学习系列——Maven Web项目中集成使用Spring基础之上,对之前的Maven Web项目进行升级改造,实现对MySQL的数据访问. 添加依赖Jar包 这部分内容需要以下Jar包支持 mysql-connector:MySQL数据库连接驱动,架起服务端与数据库沟通的桥梁: MyBatis:一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架: log4j:Apache的开源项目,一个功能强大的日志组件,提供方便的日志记录: 修改后的pom.xm

【java下午茶系列】java三重奏之封装

java中的封装.继承.多态可谓是踏入这一行业的必经之槛,诸多新人在不明就里的情况下将其各种概念背的是滚瓜烂熟.即便是工作多年之后,也不见得能说出个所以然,或许冥冥之中已经写过无数封装的代码,只是近在眼前人不识了.也正是鉴于此,趁着有所心得,螃蟹留个印记,供大家分享与指正. 既然作为一个安全系数很高的编程语言,内部结构及权限分配必然有着独到之处,就像是初来乍到就碰到的private.public.protected,一头雾水,虽然用几分钟时间弄的很明白,但以后的很长时间都是处于混乱状态,直到有一

[转]Java多线程干货系列—(一)Java多线程基础

Java多线程干货系列—(一)Java多线程基础 字数7618 阅读1875 评论21 喜欢86 前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧. 正文 线程与进程 1 线程:进程中负责程序执行的执行单元线程本身依靠程序进行运行线程是程序中的顺序控制流,只能使用分配给程序的资源和环境 2 进程:执行中的程序一个进程至少包含一个线程 3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程 4

Java命令学习系列(7):Javap(转)

原文出处: Hollis(@Hollis_Chuang) javap是jdk自带的一个工具,可以对代码反编译,也可以查看java编译器生成的字节码. 一般情况下,很少有人使用javap对class文件进行反编译,因为有很多成熟的反编译工具可以使用,比如jad.但是,javap还可以查看java编译器为我们生成的字节码.通过它,可以对照源代码和字节码,从而了解很多编译器内部的工作. 实例 javap命令分解一个class文件,它根据options来决定到底输出什么.如果没有使用options,那么