ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Effective Java 读书笔记(四)

2021-06-06 17:59:04  阅读:234  来源: 互联网

标签:WeakHashMap Java Effective 读书笔记 键值 new Entry ReferenceQueue size


前言:

又又来更新了,鸽王了害,自从工作了之后一直很少花时间看书了,最近搬了新的家环境一变,又有了点儿兴致hh,大概率更新的节奏是每周一篇吧,工作日要上班蛮累的。。。希望自己可以坚持下去吧~

Item07:Eliminate expired object references 消除过期的对象引用

JAVA中有自动的回收功能,但某些地方如果书写的代码不好仍然会出现内存泄漏。这里举个例子:栈实现

public class MemoryLeakStack {
    private Object[] elements;
    private int size;
    private static final int DEFAULT_INITIAL_CAPACITY=16;

    public MemoryLeakStack(){
        elements=new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e){
        ensureCapacity();
        elements[size++]=e;
    }

    public Object pop(){
        if (size==0){
            throw new EmptyStackException();
        }
        return elements[--size];
    }

    private void ensureCapacity() {
        if (elements.length==size){
            //返回一个新的数组对象,复制旧数组且扩大长度
            elements= Arrays.copyOf(elements,2*size+1);
        }
    }

}

一眼看去,貌似没啥问题,但是其实在pop方法中存在着安全隐患,它只是仅仅将stack的长度size–,而并没有将数组中的对象引用消除,导致会存在一直无法使用的对象存活在内存中。

这里的改进方式是:

  public Object pop(){
        if (size==0){
            throw new EmptyStackException();
        }
        Object result= elements[--size];
    		elements[size]=null; //消除引用
    		return result;
    }

虽然看起来很简单且java也提供了自动回收机制,但日常开发中还是要注意消除引用。

那么JAVA中有哪些场景需要注意的呢?

1、只要类是自己管理内存,就需要警惕。例如刚刚举的Stack例子
2、对象存放到缓存。

需要注意的是当对象引用放入缓存中,如果它很久不会被使用,那么该引用应该进行消除。解决方法:可以使用WeakHashMap代表缓存。

WeakHashMap细说

HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null
不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。某个键被移除时,它对应的键值对也就从映射中有效地移除了。
这个“弱键”的原理呢?大致上就是,通过WeakReference和ReferenceQueue实现的

WeakHashMap类的重要内容:

public class WeakHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V> {
    //...
    Entry<K,V>[] table;
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
}

可以看出WeakHashMap是使用一个Enrty数组存储数据。

还有一个用于存储被gc回收后的弱引用的队列ReferenceQueue

WeakHashMap中的Enrty:

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
}

可以看出其继承了WeakReference类。代表着存储的Entry数组是一个软引用数组

到底是如何利用这个WeakReference以及ReferenceQueue呢?

WeakHashMap代码片段:

/**
 * Expunges stale entries from the table.
 删除旧entry
 */
private void expungeStaleEntries() {
    for (Object x; (x = queue.poll()) != null; ) {
        synchronized (queue) {
            @SuppressWarnings("unchecked")
          			//获取ReferenceQueue队列中元素的引用。即已经被gc清除的健值对
                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
                    //将value的引用设置为null
                    e.value = null; // Help GC
                    size--;
                    break;
                }
                prev = p;
                p = next;
            }
        }
    }
}

具体的操作就是GC每次清理掉一个对象之后,引用对象会被放到ReferenceQueue中。expungeStaleEntries方法:遍历ReferenceQueue队列,将Enrty数组中对应的Entry删除,并且将其value设置为null加速Gc的过程。减少对value的引用,方便其后续被回收。

  • 新建WeakHashMap,将“键值对”添加到WeakHashMap中。
    实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
  • 某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
  • 当我们每次需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对
public class ReferenceQueueTest {
    public static void main(String[] args) throws InterruptedException {
        int _1M = 1024 * 1024;

        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
        Thread thread = new Thread(() -> {
            try {
                int cnt = 0;
                WeakReference<byte[]> k;
                while ((k = (WeakReference) referenceQueue.remove()) != null) {
                    System.out.println((cnt++) + "回收了:" + k);
                }
            } catch (InterruptedException e) {
                // 结束循环
            }
        });
        //设置为守护线程,,当系统中全部都是守护线程的时候就会退出
        thread.setDaemon(true);
        thread.start();

        Object value = new Object();
        Map<Object, Object> map = new HashMap<>();
        for (int i = 0; i < 100; i++) {
            byte[] bytes = new byte[_1M];
            WeakReference<byte[]> weakReference = new WeakReference<byte[]>(bytes, referenceQueue);
            map.put(weakReference, value);
        }
        System.out.println("map.size->" + map.size());
    }
}
3、监听器以及其他回调。

标签:WeakHashMap,Java,Effective,读书笔记,键值,new,Entry,ReferenceQueue,size
来源: https://blog.csdn.net/awake_lqh/article/details/117632819

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有