天天看点

Java中的引用类型和使用场景

作者:Grey

原文地址:Java中的引用类型和使用场景

Java中的引用类型分成强引用, 软引用, 弱引用, 虚引用。
没有引用指向这个对象,垃圾回收会回收
当有一个对象被一个软引用所指向的时候,只有系统内存不够用的时候,才会被回收,可以用做缓存(比如缓存大图片)

示例如下代码:注:执行以下方法的时候,需要把VM options设置为<code>-Xms20M -Xmx20M</code>。

上述代码在第一次执行<code>System.out.println(reference.get())</code>时候,由于堆的最大最小值都是<code>20M</code>,而我们分配的<code>byte</code>数组是<code>10M</code>,没有超过最大堆内存,所以执行垃圾回收,软引用不被回收,后续又调用了<code>byte[] bytes = new byte[1024 * 1024 * 10];</code>再次分配了<code>10M</code>内存,此时堆内存已经超过设置的最大值,会进行回收,所以最后一步的<code>System.out.println(reference.get());</code>无法<code>get</code>到数据。

只要垃圾回收,就会回收。如果有一个强引用指向弱引用中的这个对象,如果这个强引用消失,这个对象就应该被回收。一般用在容器里面。

代码示例如下:

如果执行了一次<code>GC</code>,<code>reference.get()</code> 获取到的值即为空。

弱引用的一个典型应用场景就是<code>ThreadLocal</code>,以下是<code>ThreadLocal</code>的的简要介绍

set方法

get方法

<code>ThreadLocalMap</code>是当前线程的一个成员变量,所以,其他线程无法读取当前线程设置的<code>ThreadLocal</code>值。

<code>ThreadLocal</code>的主要应用场景

场景一:每个线程需要一个独享的对象:假设有100个线程都需要用到<code>SimpleDateFormat</code>类来处理日期格式,如果共用一个<code>SimpleDateFormat</code>,就会出现线程安全问题,导致数据出错,如果加锁,就会降低性能,此时使用<code>ThreadLocal</code>,给每个线程保存一份自己的本地<code>SimpleDateFormat</code>,就可以同时保证线程安全和性能需求。

场景二:每个线程内部保存全局变量,避免传参麻烦:假设一个线程的作用是拿到前端用户信息,逐层执行<code>Service1</code>,<code>Service2</code>,<code>Service3</code>,<code>Service4</code>层的业务逻辑,其中每个业务层都会用到用户信息,此时一个解决办法就是将<code>User</code>信息对象作为参数层层传递,但是这样会导致代码冗余且不利于维护。此时可以将<code>User</code>信息对象放入当前线程的<code>Threadlocal</code>中,就变成了全局变量,在每一层业务层中,需要使用的时候直接从<code>Threadlocal</code>中获取即可。

场景三:<code>Spring</code>的声明式事务,数据库连接写在配置文件,多个方法可以支持一个完整的事务,保证多个方法是用的同一个数据库连接(其实就是放在<code>ThreadLocal</code>里面)

了解了<code>ThreadLocal</code>简要介绍以后,我们可以深入理解一下<code>ThreadLocal</code>的一个内部原理,前面提到,<code>ThreadLocal</code>的<code>set</code>方法实际上是往当前线程的一个<code>threadLocals</code>表中插入一条记录,而这个表中的记录都存在一个<code>Entry</code>对象中,这个对象有一个key和一个value,<code>key</code>就是当前线程的<code>ThreadLocal</code>对象。

这个<code>Entry</code>对象继承了<code>WeakReference</code>, 且构造函数调用了<code>super(k)</code>, 所以<code>Entry</code>中的<code>key</code>是通过一个弱引用指向的<code>ThreadLocal</code>,所以,我们在主方法中调用

<code>tl</code>是通过强引用指向这个<code>ThreadLocal</code>对象。

当前线程的<code>threadLocalMap</code>中的<code>key</code>是通过弱引用指向<code>ThreadLocal</code>对象,这样就可以保证,在<code>tl</code>指向空以后,这个<code>ThreadLocal</code>会被回收,否则,如果<code>threadLocalMap</code>中的<code>key</code>是强引用指向<code>ThreadLocal</code>对象话,这个<code>ThreadLocal</code>对象永远不会被回收。就会导致内存泄漏。

但是,即便<code>key</code>用弱引用指向<code>ThreadLocal</code>对象,<code>key</code>值被回收后,<code>Entry</code>中的<code>value</code>值就无法被访问到了,且<code>value</code>是通过强引用关联,所以,也会导致内存泄漏,所以,每次在<code>ThreadLocal</code>中的对象不用了,记得要调用<code>remove</code>方法,把对应的<code>value</code>也给清掉。

用于管理堆外内存回收

虚引用关联了一个对象,以及一个队列,只要垃圾回收,虚引用就被回收,一旦虚引用被回收,虚引用会被装到这个队列,并会收到一个通知(如果有值入队列,会得到一个通知)所以,如果想知道虚引用何时被回收,就只需要不断监控这个队列是否有元素加入进来了。

虚引用里面关联的对象用get方法是无法获取的。

JDK的<code>NIO</code>包中有一个<code>DirectByteBuffer</code>, 这个<code>buffer</code>指向的是堆外内存,所以当这个<code>buffer</code>设置为空的时候,Java的垃圾回收无法回收,所以,可以用虚引用来管理这个<code>buffer</code>,当我们检测到这个虚引用被垃圾回收器回收的时候,可以做出相应的处理,去回收堆外内存。

juc