在 JDK.1.2
之后,Java
对引用的概念进行了扩充,将引用分为了:
(Strong Reference)
(Soft Reference)
(Weak Reference)
(Phantom Reference)
4 种,这 4 种引用的强度依次减弱。不同的引用在垃圾回收中体现也是不一样~我们先创建一个M对象,后面为了方便的感受GC的情况。
package com.cyblogs.java.learning.C001_ReferenceType;
/**
* Created with java-learning-demo
*
* @description:
* @author: chenyuan
* @date: 2020/6/16
* @time: 9:30 PM
*/
public class M {
@Override
public void finalize() {
System.out.println("finalize");
}
}
finalize
函数是对象在gc
的时候,一定会调用该方法。我们重写一下该方法并且打印一行日志。
只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM
也会直接抛出OutOfMemoryError
,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null
,这样一来,JVM
就可以适时的回收对象了。
package com.cyblogs.java.learning.C001_ReferenceType;
import java.io.IOException;
/**
* Created with java-learning-demo
*
* @description: 正常引用/强引用
* @author: chenyuan
* @date: 2020/6/16
* @time: 9:31 PM
*/
public class C001_01_NormalReference {
public static void main(String[] args) throws IOException {
M m = new M();
// 将对象复制为空
m = null;
// 手动触发GC
System.gc();
// 因为不是触发gc就一定会立马gc,所以让线程阻塞一下
System.in.read();
}
}
控制台日志输出:
Connected to the target VM, address: '127.0.0.1:53621', transport: 'socket'
finalize
Disconnected from the target VM, address: '127.0.0.1:53621', transport: 'socket'
软引用是用来描述一些非必需但仍有用的对象。「在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常」。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。
package com.cyblogs.java.learning.C001_ReferenceType;
import java.lang.ref.SoftReference;
/**
* Created with java-learning-demo
*
* @description: 软引用:当内存不足的时候,gc才会回收。非常适合做缓存
* @author: chenyuan
* @date: 2020/6/16
* @time: 9:31 PM
*/
public class C001_02_SoftReference {
public static void main(String[] args) throws InterruptedException {
// 开辟一个20M的空间
SoftReference<byte[]> m = new SoftReference<byte[]>(new byte[1024 * 1024 * 10]);
System.out.println(m.get());
// 手动GC一下,看是否可以GC掉
System.gc();
// 避免gc不会立马触发,尝试休眠1s
Thread.sleep(1000);
// 然后再尝试获取
System.out.println(m.get());
// 重新开辟一个空间
byte[] b = new byte[1024 * 1024 * 15];
System.out.println(m.get());
}
}
因为它是在内存不足的时候才会触发,所以我们在跑之前需要设置一下最大堆。
-Xmx20M
控制台日志输出:
Connected to the target VM, address: '127.0.0.1:54335', transport: 'socket'
[B@4c3e4790
[B@4c3e4790
null
Disconnected from the target VM, address: '127.0.0.1:54335', transport: 'socket'
你会发现就算我们gc
了,后面还是会get
得到,因为空间还足够。当后面byte[] b
再继续申请空间的时候,发现空间不足了,这个时候就会触发gc
动作,把软引用的部分清除掉。
弱引用的引用强度比软引用要更弱一些,「无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收」。
package com.cyblogs.java.learning.C001_ReferenceType;
import java.lang.ref.WeakReference;
/**
* Created with java-learning-demo
*
* @description: 弱引用:是为了解决某些地方的内存泄露问题
* @author: chenyuan
* @date: 2020/6/16
* @time: 9:32 PM
*/
public class C001_03_WeakReference {
public static void main(String[] args) {
WeakReference<M> m = new WeakReference<M>(new M());
System.out.println(m.get());
System.gc();
System.out.println(m.get());
ThreadLocal<M> tl = new ThreadLocal<M>();
tl.set(new M());
tl.remove();
}
}
控制台日志输出:
Connected to the target VM, address: '127.0.0.1:55151', transport: 'socket'
com.cyblogs.java.learning.C001_ReferenceType.M@38cccef
null
finalize
Disconnected from the target VM, address: '127.0.0.1:55151', transport: 'socket'
我们看一下ThreadLocal
的set
方法:
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
http://static.cyblogs.com/QQ20200616-225739@2x.jpg
为什么Entry
要使用弱引用?
tl=null
,但是key
的引用还是指向ThreadLocal
。所以内存会泄露~而弱引用不会ThreadLocal
被回收。key
的值变成了null
,则导致value
的值再也无法被访问到,因此依然存在内存泄露问题。虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。
package com.cyblogs.java.learning.C001_ReferenceType;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.LinkedList;
import java.util.List;
/**
* Created with java-learning-demo
*
* @description: 虚引用:
* @author: chenyuan
* @date: 2020/6/16
* @time: 9:33 PM
*/
public class C001_04_PhantomReference {
private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<M>();
private static final List<Object> LIST = new LinkedList<Object>();
public static void main(String[] args) throws InterruptedException {
PhantomReference<M> m = new PhantomReference<M>(new M(), QUEUE);
// 线程1
new Thread(() -> {
while (true){
LIST.add(new byte[1024 * 1024]);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(m.get());
}
}).start();
// 线程2
new Thread(()->{
while (true) {
Reference<? extends M> poll = QUEUE.poll();
if (poll != null) {
System.out.println("虚引用对象被JVM回收了" + poll);
}
}
}).start();
Thread.sleep(500);
}
}
在零拷贝中就会使用到虚引用,但我们又无法去操作对外的内存。因为太弱了,我们也无法感知到~ 这里就需要利用到ReferenceQueue
。
http://static.cyblogs.com/QQ20200616-222909@2x.jpg
如果大家喜欢我的文章,可以关注个人订阅号。欢迎随时留言、交流。如果想加入微信群的话一起讨论的话,请加管理员简栈文化-小助手(lastpass4u),他会拉你们进群。