Java引用类型
Java 中有强、软、弱、虚四种引用类型。强引用这个类并不存在,默认对象都是强引用类型,相比于WeakReference、SoftReference、PhantomReference的衬托,所以才起了一个叫Strong Reference的名字。其类图如下所示:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/671867fafdb73603f5226e85201f2dd8.png)
强引用
释义
如果JVM垃圾回收器 GC 可达性分析结果为可达,表示引用类型仍然被引用着,这类对象始终不会被垃圾回收器回收,即使JVM发生OOM也不会回收。而如果 GC 的可达性分析结果为不可达,那么在GC时会被回收。
代码演示
强引用的使用示例如下:String strong = "strongReference";
软引用
释义
软引用是一种比强引用生命周期稍弱的一种引用类型。在JVM内存充足的情况下,软引用并不会被垃圾回收器回收,只有在JVM内存不足的情况下,才会被垃圾回收器回收。
代码演示
public void testSoftReference(){
// 分配 10 M 内存给 soft
SoftReference<byte[]> soft = new SoftReference<>(new byte[1024*1024*10]);
// 查看 soft 对象
System.out.println("new: "+soft.get());
// 当内存充足时,建议进行一次gc
System.gc();
// 查看gc后 soft对象
System.out.println("第一次gc: "+soft.get());
// 在此申请一个强引用类型,使得分配的内存之和大于jvm最大可使用内存
byte[] strong = new byte[1024*1024*10];
// 查看soft对象
System.out.println("内存不足: "+soft.get());
}
在执行代码前,通过-xms15m设置最大堆内存为15M,这是关键,如果不设置这步,会无法复现下方结果。
打印结果为:
通过示例可以看到当第一次gc之后,因为堆内存足够,因此soft对象没有被回收,但是当内存不足时,soft的对象就被回收了。
用途
所以软引用一般用来实现一些内存敏感的缓存,只要内存空间足够,对象就会保持不被回收掉。例如在处理图像时对大的图像文件进行缓存。
其他问题
- 当内存不足时,如果我声明的是另一个软引用什么是一个虚引用,第一个软引用会被回收吗?
答案: 是
弱引用
释义
弱引用是一种比软引用生命周期更短的引用。它的生命周期很短,不论当前内存是否充足,都只能存活到下一次垃圾收集之前。
此外,弱引用可以和一个引用队列联合使用,如果弱引用引用的对象被垃圾回收,JVM会把这个弱引用加入到与之关联的引用队列中。
实例
弱引用一个典型的用法就是在ThreadLocal中的使用。具体示例可以查看:
ThreadLocal 简介
虚引用
释义
与弱引用相比,虚引用是比弱引用更弱的引用,甚至无法通过虚引用无法通过get方法来获得指向的值,源码中直接返回了null
;
/**
* Returns this reference object's referent. Because the referent of a
* phantom reference is always inaccessible, this method always returns
* <code>null</code>.
*
* @return <code>null</code>
*/
public T get() {
return null;
}
相比于弱引用,虚引用在使用时必须和一个ReferenceQueue一起使用,当虚引用指向的对象被GC收集时,这个对象并不会被马上释放,此时PhantomReference会被放入到关联的ReferenceQueue中去,只有当处理了对应队列中的虚引用之后,对象占用的空间才会被正式释放。
注意: 在Java 8以及之前的版本中,在虚引用回收后,虚引用指向的对象才会回收。在Java 9以及更新的版本中,虚引用不会对对象的生存产生任何影响。
代码示例
public class TestReference {
private static final List<Object> list = new ArrayList<>();
private static final ReferenceQueue<M> RQ = new ReferenceQueue();
static class M{
@Override
protected void finalize() throws Throwable {
System.out.println("我被回收了");
}
}
public void testPhantomReference(){
PhantomReference<M> phantomReference = new PhantomReference(new M() ,RQ);
System.out.println("获取虚引用对象:"+ phantomReference.get());
new Thread(()->{
while(true){
list.add(new byte[1024*1024*2]);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
while (true) {
Reference poll = RQ.poll();
if (poll != null) {
System.out.println("虚引用被回收了:" + poll);
}
}
}).start();
// 阻塞进程
Scanner scanner = new Scanner(System.in);
scanner.hasNext();
}
}
执行这端代码:
此时,虚函数指向的对象M的finalize方法已经被执行了,即对象已经被回收了,同时在队列中也取到了poll函数。
VisualVM查看内存情况,显式清除虚引用前后内存占用情况
另外还可以通过visualVM来查看对应的JVM内存占用情况:
static class M{
byte[] value = new byte[1024*1024*6];
@Override
protected void finalize() throws Throwable {
System.out.println("我被回收了");
}
}
public void testPhantomReference2(){
PhantomReference<M> phantomReference = new PhantomReference(new M() ,RQ);
System.gc();
// phantomReferenc
// Reference poll
// poll = null;
System.gc();
byte[] b = new byte[1024*1024*8];
Scanner scanner = new Scanner(System.in);
scanner.hasNext();
}
当执行这段这段代码时,即使执行了System.gc(), 虚引用指向的老年代中保存这M中声明的6M的变量还是存在,当新申明8M的空间时,由于空间不够,因此直接OOM报错。
在注释存在的情况下,进行gc:
去掉注释,显式置空虚引用,继续gc