之前我们提到过 GC, 但当 Java 中引用的对象越来越多, 会导致内存空间不足, 最终会产生错误 OutOfMemoryError, 并让应用程序终止. 那为什么 GC 在此时不能多收集一些对象呢? 这就和今天说的引用类型有关了.
首先, 从 JDK1.2 开始, 对象的引用被划分为 4 种级别, 从而使程序能更加灵活地控制对象的生命周期. 这 4 种级别由高到低依次为: 强引用, 软引用, 弱引用和虚引用.
强引用
强引用 (Strong Reference) 是使用最普遍的引用. 如果一个对象具有强引用, 那么它永远不会被 GC. 例如:
Object strongReference = new Object();
当内存空间不足时, JVM 宁愿抛出 OutOfMemoryError, 使程序异常终止, 也不会靠随意回收具有强引用的对象来解决内存不足的问题.
如果强引用对象不使用时, 需要弱化从而可以被 GC, 例如 ArrayList 中的 clear()方法:
- /**
- * Removes all of the elements from this list. The list will
- * be empty after this call returns.
- */
- public void clear() {
- modCount++;
- // clear to let GC do its work
- for (int i = 0; i <size; i++)
- elementData[i] = null;
- size = 0;
- }
显式地设置强引用对象为 null, 或让其超出对象的生命周期范围, 则垃圾回收器认为该对象不存在引用, 就会回收这个对象. 具体什么时候收集这要取决于具体的垃圾回收器.
软引用
如果一个对象只具有软引用(Soft Reference), 当内存空间充足时, 垃圾回收器就不会回收它; 如果内存空间不足了, 就会回收这些对象的内存. 只要垃圾回收器没有回收它, 该对象就可以被程序使用. 让我们来看一个例子具体了解一下:
- String str = new String("abc");
- SoftReference<String> softReference = new SoftReference<>(str);
- String result = softReference.get();
让我们来看一下 get():
- public T get() {
- T o = super.get();
- // timestamp 代表上一次软引用上一次被使用的时间(初始化, get())
- // clock 代表上一次 GC 的时间
- if (o != null && this.timestamp != clock)
- this.timestamp = clock;
- return o;
- }
因此, 软引用在被垃圾回收时, 也遵循 LRU 法则, 优先回收最近最少被使用的对象进行回收.
软引用的使用场景多是内存敏感的高速缓存. 具体来说, 就是我们希望将数据存放到缓存中, 这样可以快速进行读取. 但是, 当 JVM 中内存不够用时, 我们又不希望缓存数据会占用到 JVM 的内存. 例如配合 ReferenceQueue, 如果软引用所引用对象被垃圾回收, JVM 就会把这个软引用加入到与之关联的引用队列中:
- ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
- String str = new String("abc");
- SoftReference<String> softReference = new SoftReference<>(str, referenceQueue);
- str = null;
- // Notify GC
- System.gc();
- System.out.println(softReference.get()); // abc
- Reference<? extends String> reference = referenceQueue.poll();
- System.out.println(reference); //null
但是需要注意的是, 如果使用软引用缓存, 有可能导致 Full GC 增多.
弱引用
如果一个对象只具有弱引用(Weak Reference), 其生命周期相比于软引用更加短暂. 在垃圾回收器线程扫描它所管辖的内存区域的过程中, 一旦发现了只具有弱引用的对象, 不管当前内存空间足够与否, 都会对它进行回收. 不过, 由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象. 其使用为:
- String str = new String("abc");
- WeakReference<String> weakReference = new WeakReference<>(str);
- str = weakReference.get();
讲到弱引用, 就不得不提到 WeakHashMap. 和 HashMap 相比, 当我们给 JVM 分配的内存不足的时候, HashMap 宁可抛出 OutOfMemoryError 异常, 也不会回收其相应的没有被引用的对象, 而 WeakHashMap 则会回收存储在其中但有被引用的对象.
WeakHashMap 通过将一些没有被引用的键的值赋值为 null , 这样的话就会告知 GC 去回收这些存储的值了. 假如我们特地传入 key 为 null 的键, WeakHashMap 会将键设置为特殊的 Oject, 源码为:
- public V put(K key, V value) {
- // key 会被重新赋值
- Object k = maskNull(key);
- int h = hash(k);
- Entry<K,V>[] tab = getTable();
- int i = indexFor(h, tab.length);
- 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];
- tab[i] = new Entry<>(k, value, queue, h, e);
- if (++size>= threshold)
- resize(tab.length * 2);
- return null;
- }
- /**
- * Value representing null keys inside tables.
- * 特殊的 key
- */
- private static final Object NULL_KEY = new Object();
- /**
- * Use NULL_KEY for key if it is null.
- */
- private static Object maskNull(Object key) {
- return (key == null) ? NULL_KEY : key;
- }
虚引用
虚引用(PhantomReference), 顾名思义, 就是形同虚设. 与其他几种引用都不同, 虚引用并不会决定对象的生命周期. 如果一个对象仅持有虚引用, 那么它就和没有任何引用一样, 在任何时候都可能被垃圾回收器回收.
虚引用主要用来跟踪对象被垃圾回收器回收的活动. 虚引用与软引用和弱引用的一个区别在于:
虚引用必须和引用队列 (ReferenceQueue) 联合使用. 当垃圾回收器准备回收一个对象时, 如果发现它还有虚引用, 就会在回收对象的内存之前, 把这个虚引用加入到与之关联的引用队列中.
例如:
- String str = new String("abc");
- ReferenceQueue queue = new ReferenceQueue();
- // 创建虚引用, 要求必须与一个引用队列关联
- PhantomReference pr = new PhantomReference(str, queue);
程序可以通过判断引用队列中是否已经加入了虚引用, 来了解被引用的对象是否将要进行垃圾回收. 如果程序发现某个虚引用已经被加入到引用队列, 那么就可以在所引用的对象的内存被回收之前采取必要的行动, 也可以理解为一种回调方法.
总结
Java 中 4 种引用的级别和强度由高到低依次为: 强引用 -> 软引用 -> 弱引用 -> 虚引用
通过表格, 说明其特性:
引用类型 | 被垃圾回收的时间 | 使用场景 | 生存时间 |
---|---|---|---|
强引用 | 从来不会 | 对象的一般状态 | JVM 停止运行时 |
软引用 | 内存不足时 | 对象缓存 | 内存不足时 |
弱引用 | 正常垃圾回收时 | 对象缓存 | 垃圾回收后终止 |
虚引用 | 正常垃圾回收时 | 跟踪对象的垃圾回收 | 垃圾回收后终止 |
有兴趣的话可以访问我的博客或者关注我的公众号, 头条号, 说不定会有意外的惊喜.
https://death00.github.io/
来源: https://www.cnblogs.com/death00/p/11718771.html