Java中的引用类型和使用场景 Java中的引用类型和使用场景详细
Grey 人气:0Java中的引用类型有哪几种?
Java中的引用类型分成 强引用 , 软引用 , 弱引用 , 虚引用 。
1、强引用
没有引用指向这个对象,垃圾回收会回收
package git.snippets.juc; import java.io.IOException; public class NormalRef { public static void main(String[] args) throws IOException { M m = new M(); m = null; System.gc(); System.in.read(); } static class M { M() {} @Override protected void finalize() throws Throwable { System.out.println("finalized"); } } }
2、软引用
当有一个对象被一个软引用所指向的时候,只有系统内存不够用的时候,才会被回收,可以用做缓存(比如缓存大图片)
示例如下代码:注:执行以下方法的时候,需要把VM options
设置为 -Xms20M -Xmx20M
。
package git.snippets.juc; import java.io.IOException; import java.lang.ref.SoftReference; import java.util.concurrent.TimeUnit; /** * heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用干掉 * 软引用,适合做缓存 * 示例需要把Vm options设置为:-Xms20M -Xmx20M */ public class SoftRef { public static void main(String[] args) throws IOException { SoftReference<byte[]> reference = new SoftReference<>(new byte[1024 * 1024 * 10]); System.out.println(reference.get()); System.gc(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(reference.get()); byte[] bytes = new byte[1024 * 1024 * 10]; System.out.println(reference.get()); System.in.read(); } }
上述代码在第一次执行 System.out.println(reference.get())
时候,由于堆的最大最小值都是 20M ,而我们分配的 byte
数组是 10M ,没有超过最大堆内存,所以执行垃圾回收,软引用不被回收,后续又调用了 byte[] bytes = new byte[1024 * 1024 * 10]
; 再次分配了 10M 内存,此时堆内存已经超过设置的最大值,会进行回收,所以最后一步的 System.out.println(reference.get());
无法 get 到数据。
3、弱引用
只要垃圾回收,就会回收。如果有一个强引用指向弱引用中的这个对象,如果这个强引用消失,这个对象就应该被回收。一般用在容器里面。
代码示例如下:
package git.snippets.juc; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.concurrent.TimeUnit; /** * 弱引用遭到gc就会回收 * ThreadLocal应用,缓存应用,WeakHashMap */ public class WeakRef { public static void main(String[] args) { WeakReference<T> reference = new WeakReference<>(new T()); System.out.println(reference.get()); System.gc(); System.out.println(reference.get()); } static class T { T() {} @Override protected void finalize() { System.out.println("finalized"); } } }
如果执行了一次 GC
, reference.get()
获取到的值即为空。
4、弱引用的使用场景
弱引用的一个典型应用场景就是 ThreadLocal
,以下是 ThreadLocal
的的简要介绍
set方法:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
get方法:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
ThreadLocalMap
是当前线程的一个成员变量,所以,其他线程无法读取当前线程设置的 ThreadLocal
值。
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal
的主要应用场景
场景一:每个线程需要一个独享的对象:假设有100个线程都需要用到 SimpleDateFormat
类来处理日期格式,如果共用一个 SimpleDateFormat
,就会出现线程安全问题,导致数据出错,如果加锁,就会降低性能,此时使用 ThreadLocal
,给每个线程保存一份自己的本地 SimpleDateFormat
,就可以同时保证线程安全和性能需求。
场景二:每个线程内部保存全局变量,避免传参麻烦:假设一个线程的作用是拿到前端用户信息,逐层执行 Service1
, Service2
, Service3
, Service4
层的业务逻辑,其中每个业务层都会用到用户信息,此时一个解决办法就是将 User 信息对象作为参数层层传递,但是这样会导致代码冗余且不利于维护。此时可以将 User 信息对象放入当前线程的 Threadlocal 中,就变成了全局变量,在每一层业务层中,需要使用的时候直接从 Threadlocal 中获取即可。
场景三: Spring
的声明式事务,数据库连接写在配置文件,多个方法可以支持一个完整的事务,保证多个方法是用的同一个数据库连接(其实就是放在 ThreadLocal
里面)
了解了 ThreadLocal
简要介绍以后,我们可以深入理解一下 ThreadLocal
的一个内部原理,前面提到, ThreadLocal
的 set 方法实际上是往当前线程的一个 threadLocals
表中插入一条记录,而这个表中的记录都存在一个 Entry 对象中,这个对象有一个key和一个value, key 就是当前线程的 ThreadLocal
对象。
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
这个 Entry 对象继承了 WeakReference
, 且构造函数调用了 super(k)
, 所以 Entry
中的 key 是通过一个弱引用指向的 ThreadLocal
,所以,我们在主方法中调用
ThreadLocal<Object> tl = new ThreadLocal<>();
tl 是通过强引用指向这个 ThreadLocal
对象。
当前线程的 threadLocalMap
中的 key 是通过弱引用指向 ThreadLocal 对象,这样就可以保证,在 tl 指向空以后,这个 ThreadLocal 会被回收,否则,如果 threadLocalMap
中的 key 是强引用指向 ThreadLocal
对象话,这个 ThreadLocal
对象永远不会被回收。就会导致内存泄漏。
但是,即便 key 用弱引用指向 ThreadLocal 对象, key 值被回收后, Entry 中的 value 值就无法被访问到了,且 value 是通过强引用关联,所以,也会导致内存泄漏,所以,每次在 ThreadLocal
中的对象不用了,记得要调用 remove
方法,把对应的 value 也给清掉。
5、虚引用
用于管理堆外内存回收
虚引用关联了一个对象,以及一个队列,只要垃圾回收,虚引用就被回收,一旦虚引用被回收,虚引用会被装到这个队列,并会收到一个通知(如果有值入队列,会得到一个通知)所以,如果想知道虚引用何时被回收,就只需要不断监控这个队列是否有元素加入进来了。
虚引用里面关联的对象用get方法是无法获取的。
import java.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.util.LinkedList; import java.util.List; // 配置 -Xms20M -Xmx20M public class PhantomRef { private static final List<Object> LIST = new LinkedList<>(); private static final ReferenceQueue<P> QUEUE = new ReferenceQueue<>(); public static void main(String[] args) { PhantomReference<P> phantomReference = new PhantomReference<>(new P(), QUEUE); new Thread(() -> { while (true) { LIST.add(new byte[1024 * 1024]); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } System.out.println(phantomReference.get()); } }).start(); new Thread(() -> { while (true) { Reference<? extends P> poll = QUEUE.poll(); if (poll != null) { System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll); } } }).start(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } static class P { @Override protected void finalize() throws Throwable { System.out.println("finalized"); } } }
6、虚引用的应用场景
JDK的 NIO 包中有一个 DirectByteBuffer
, 这个 buffer
指向的是堆外内存,所以当这个 buffer
设置为空的时候,Java的垃圾回收无法回收,所以,可以用虚引用来管理这个 buffer
,当我们检测到这个虚引用被垃圾回收器回收的时候,可以做出相应的处理,去回收堆外内存。
加载全部内容