浅读java.util.Map及其实现类(五)

时间:2021-06-01 19:38:56


强引用,弱引用,软引用,虚引用


强引用

哪怕内存不足也不会回收的引用,真是真爱
一些常用的初始慢的数据可以放到强引用中,以免下次其他访客使用时再次进行漫长的初始 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

弱引用

只要被垃圾回收扫描到,就回收它。。
以下代码,我put了5000次,最后输出只有2000多。当然我特意修改了我的jvm内存配置
当然也可以吧内存调高了手动调用System.gc
static int i = 10;
public static void main(String[] args) throws OpenDataException {
memory();
WeakHashMap<Integer, Integer> wHM = new WeakHashMap<Integer, Integer>();
for(int i=0;i<5000;i++){
wHM.put(i, new Integer(new Random().nextInt(11111)));
}
memory();
System.out.println(wHM.size());
}

private static void memory() {
// 可使用内存
long totalMemory = Runtime.getRuntime().totalMemory() >> i;
// 剩余内存
long freeMemory = Runtime.getRuntime().freeMemory() >> i;
// 最大可使用内存
long maxMemory = Runtime.getRuntime().maxMemory() >> i;
System.out.printf("可用内存:%dkb,剩余内存:%dkb,最大可用内存:%dkb \n", totalMemory, freeMemory, maxMemory);
}

可用内存:1536kb,剩余内存:730kb,最大可用内存:1536kb 
可用内存:1536kb,剩余内存:337kb,最大可用内存:1536kb 
2656

软引用

类似弱引用,不过是他是在当内存不足时,才去回收,所以比弱引用的生存环境好一些

虚引用

与弱引用类似,只不过他是通过PhantomRefernce创建,对象被回收后放到ReferenceQueue中,等待你的进一步操作
比finalize更灵活
public static void main(String[] args) throws OpenDataException {
Integer z=new Integer(1234);
ReferenceQueue<Integer> r=new ReferenceQueue<>();
PhantomReference<Integer> p=new PhantomReference<Integer>(z, r);
System.out.println(p.get());
System.out.println(r.poll());
z=null;
System.gc();
System.out.println(p.get());
System.out.println(r.poll());
}

Reference

说到引用不得不说Reference,它是3种引用的抽象基类
PhantomReference 虚引用
SoftReference 软引用
WeakReference 弱引用
看一个例子一目了然
	static int i = 10;
static Integer z = null;
static ReferenceQueue<Integer> queue = null;

public static void main(String[] args) throws OpenDataException {
//软引用,当内存不足的时候才会销毁它gc
ini();
core(new SoftReference<Integer>(z, queue), "软引用");
//弱引用扫到就销毁
ini();
core(new WeakReference<Integer>(z, queue), "弱引用");
//扫到就销毁,虚引用的值是get不到的,只有在销毁后从队列中得到
ini();
core(new PhantomReference<Integer>(z, queue), "虚引用");
}

private static void core(Reference<Integer> rf, String name) {
System.out.println("[" + name + " 取值] : " + rf.get());
System.out.println("[队列取值] : " + queue.poll());
z = null;
System.gc();
System.out.println("[值设置为空][调用GC]");
System.out.println("[" + name + " 取值] : " + rf.get());
System.out.println("[队列取值] : " + queue.poll());
System.out.println("=====================");
}

private static void ini() {
z = new Integer(999);
queue = new ReferenceQueue<>();
}


Console
[软引用 取值] : 999
[队列取值] : null
[值设置为空][调用GC]
[软引用 取值] : 999
[队列取值] : null
=====================
[弱引用 取值] : 999
[队列取值] : null
[值设置为空][调用GC]
[弱引用 取值] : null
[队列取值] : java.lang.ref.WeakReference@15db9742
=====================
[虚引用 取值] : null
[队列取值] : null
[值设置为空][调用GC]
[虚引用 取值] : null
[队列取值] : java.lang.ref.PhantomReference@6d06d69c
=====================

WeakHashMap


定义

K是一个可以为空的弱键(弱引用)
先看下面代码:HashMap在极限内存情况下会抛出OOM
 
static int i = 10;
public static void main(String[] args) throws OpenDataException {
memory();
HashMap<Integer, Integer> wHM = new HashMap<Integer, Integer>();
for (int i = 0; i < 50000; i++) {
wHM.put(i, new Integer(new Random().nextInt(11111)));
}
memory();
System.out.println(wHM.size());
}

private static void memory() {
// 可使用内存
long totalMemory = Runtime.getRuntime().totalMemory() >> i;
// 剩余内存
long freeMemory = Runtime.getRuntime().freeMemory() >> i;
// 最大可使用内存
long maxMemory = Runtime.getRuntime().maxMemory() >> i;
System.out.printf("可用内存:%dkb,剩余内存:%dkb,最大可用内存:%dkb \n", totalMemory, freeMemory, maxMemory);
}

可用内存:1536kb,剩余内存:730kb,最大可用内存:1536kb 
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
然而我们改成WeakHashMap就不会有OOM,但是输出的size并不是我们想要的5000,
正如上面所说的弱引用,因为内存吃紧JVM开始进行gc,扫到了弱引用自然就要消灭他们。

源码


put

public V put(K key, V value) {
Object k = maskNull(key); //maskNull ==> (key == null) ? new Object(): key;
int h = hash(k);
Entry<K,V>[] tab = getTable();
int i = indexFor(h, tab.length);
//indexFor返回了h在tab中的hashCode下面看是否是存在的hashCode来进行替换
for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
if (h == e.hash && eq(k, e.get())) {
V oldValue = e.value;
if (value != oldValue)
e.value = value;
return oldValue;
}
}
//长度增加
modCount++;
Entry<K,V> e = tab[i];
//根据i放入新值
tab[i] = new Entry<>(k, value, queue, h, e);//注意这里的queue下面的Entry来解释
if (++size >= threshold)//是否需要resize调整容器
resize(tab.length * 2);
return null;
}


Entry

 private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
 //继承了弱引用,所以WeakHashMap保存的值都是WeakReference的派生类,当然他实现了Map.Entry
V value;
final 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);
//这里调用了父级 WeakReference的构造,这个构造正是上面我们讲引用时候所用到的 
//传递一个ReferenceQueue传递一个引用队列,当值被回收队列来维持这个值 
this.value = value;
this.hash = hash;
this.next = next;
}

@SuppressWarnings("unchecked")
public K getKey() {
return (K) WeakHashMap.unmaskNull(get());
//指向了基类中的 T k,unmaskNull三目运算如果k是一个new Object()那么返回一个null
}

public V getValue() {
return value;
}

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

public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
K k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
V v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}

public int hashCode() {
K k = getKey();
V v = getValue();
return Objects.hashCode(k) ^ Objects.hashCode(v);
}

public String toString() {
return getKey() + "=" + getValue();
}
}


expungeStaleEntries

在很多操作的时候都包含了这个方法,我们来看看这个描述为“删除表中旧数据”的方法到底做了什么
 private void expungeStaleEntries() { //进行一次比对把引用队列中的垃圾数据从map中清理  
for (Object x; (x = queue.poll()) != null; ) {
//queue是引用队列 queue.poll从队列底部开始遍历
//确保引用队列是线程安全的
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) {//队列数据与table数据匹配
if (prev == e)
table[i] = next; //把当前值挤出table直接用next代替
else
prev.next = next; //改变p上一个地址值的next指针指向的内容
e.value = null; // =null帮助GC回收
size--; //容器大小变化
break;
}
prev = p;
p = next;
}
}
}
}
之后无论是resize/remove操作都会调用expungeStaleEntries()来确保已被回收的数据,在map中被清理

结语

浅读map及其实现类就到这里了
因为是浅读,所以很多地方写的比较浅,还是需要大家去自己深入了解,ConcurrentHashMap 源码还要继续啃 浅读java.util.Map及其实现类(五)