Java中的强软弱虚引用
强引用
强引用内存示意图:
Java中最普遍的引用,比如有个A类,我们使用new关键字去创建A的实例的时候
A a = new A(); 栈中变量a指向堆中A的实例这个引用就是强引用。
public class TestForceReference {
public static void main(String[] args) {
A a = new A(); // 创建出A的实例对象,变量a指向堆中的A对象实例
System.out.println(a); // 打印a对象
System.gc(); // 主动触发GC
System.out.println(a); // 打印a对象
a = null; // 断开变量a指向堆中的引用
System.gc(); // 触发GC
System.out.println(a); // 打印a对象
System.in.read(); // 阻塞主线程,给GC线程留时间回收对象
}
}
看下程序的输出结果:
com.zs.reference.A@a14482
com.zs.reference.A@a14482
null
分析:
①第一个打印是创建出A的实例并输出到控制台com.zs.reference.A@a14482;
②触发GC后,由于堆中A的实例仍然被栈中变量a引用,所以这次GC并不能回收堆中A的实例对象,输出结果com.zs.reference.A@a14482;
③a = null 这个代码将栈中a指向A实例的引用断开,此时已经没有任何引用指向A的实例,再次触发GC,堆中A的实例已经被回收了,输出结果null。、
结论:
堆中被强引用的对象(不包括循环引用)不会被GC回收。
软引用
Java中可以使用SoftReference实现软引用,软引用内存示意图:
// 虚拟机启动参数 -Xmx30m -Xms30m
public class TestSoftwareReference {
public static void main(String[] args) throws InterruptedException, IOException {
// 这里创建了一个15M大小的byte[] 会被SoftReference中的某个成员变量所引用
SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[1024 * 1024 * 15]);
// 通过get() 可以拿到这个字节数组 这里可以输出这个字节数组
System.out.println(softReference.get());
// 内存不紧张的时候触发GC
System.gc();
// 输出这个字节数组 这里也可以输出这个字节数组
System.out.println(softReference.get());
// 当再需要申请18M大小的内存时,堆中内存告急,这里会触发GC回收掉软引用
byte[] bytes = new byte[1024 * 1024 * 18];
// 已经被回收 输出null
System.out.println(softReference.get());
System.in.read(); // 阻塞主线程
}
}
控制台输出结果:
[B@a14482
[B@a14482
null
结论:当JVM内存充裕的时候,GC并不会回收软引用;当JVM内存告急的时候,GC会回收掉软引用对象。
弱引用
Java中可以使用WeakReference实现弱引用,弱引用内存示意图:
public class TestWeakReference {
public static void main(String[] args) throws IOException {
// 创建一个Object() 会被弱引用weakReference中某个成员变量所引用,这个引用
// 是java中的弱引用
WeakReference<Object> weakReference = new WeakReference<Object>(new Object());
// 也是通过get() 获取这个对象
System.out.println(weakReference.get());
// 主动触发GC
System.gc();
// 再调用get() 已经拿不到这个对象了
System.out.println(weakReference.get());
System.in.read();
}
}
结论:弱引用的对象,只要发生了GC,就会被回收!
JDK中Threadlocale中的内存泄漏BUG后来的版本中就是用弱引用解决的
虚引用
Java中的虚引用比较特殊,用PhantomReference构建一个虚引用,而且必须搭配队列ReferenceQueue使用。虚引用指向的对象,通过其get()无法获取。当一个持有虚引用的对象将要被回收的时候,JVM起先并不会直接回收这个对象,而是先把此虚引用放到关联的队列中,其他线程通过轮训这个队列,如果队列中有这个引用,说明JVM马上就要回收这个对象了,需要在这个对象真正回收之前作出相关操作。
虚引用的内存布局:
// 虚拟机参数 -Xmx20M -Xms20M
public class TestPhantomReference {
// 队列
final static ReferenceQueue<A> queue = new ReferenceQueue();
// 初始化一个集合
final static List<byte[]> list = new ArrayList<byte[]>();
public static void main(String[] args) throws IOException {
// 构建一个虚引用指向new A();
final PhantomReference<A> phantomReference = new PhantomReference<A>(new A(), queue);
// 获取不到
System.out.println(phantomReference.get());
// 此线程每秒向list中添加1m的对象,终究会把堆空间占满
new Thread(new Runnable() {
public void run() {
while (true) {
list.add(new byte[1024 * 1024]);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(phantomReference.get());
}
}
}).start();
// 这个线程一直在轮训这个队列,当内存不够触发GC的时候,GC线程会将这个虚引用放入
// 队列中告诉JVM这个虚引用的对象将要被回收了
new Thread(new Runnable() {
public void run() {
while (true) {
Reference<? extends A> poll = queue.poll();
if (poll!= null) {
System.out.println("回收");
}
}
}
}).start();
System.in.read();
}
}
程序结果:
null
null
null
null
回收 // 在这里发生了GC
null
null
虚引用一般用作堆外内存的管理,JDK的NIO包中的ByteBuffer可以通过**ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);**直接分配堆外内存。
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
DirectByteBuffer中有个成员变量Cleaner
private final Cleaner cleaner;
这个Cleaner就继承了虚引用
public class Cleaner extends PhantomReference<Object>
当这个byteBuffer成为不可达,JVM在回收这个对象时候,发现其中有个虚引用指向堆外内存,会将这个虚引用放入关联的队列中,Cleaner可以在JVM级别实现对堆外内存的释放。