新手上路,请多关照
今天我们简简单单的聊下Java的引用,新手上路难免有很多问题,还请大家多多批评指正
JAVA引用关系类图
1、强引用
当内存不足时,JVM开始垃圾回收,对于强引用对象就算是出现了OOM也不会对该对象进行回收,死都不收。
强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还‘活着’,垃圾回收器不会碰这种对象。
在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄露的主要原因之一。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显示地将相应(强)引用赋值为null,一般认为就是可以被垃圾收集的了(当然具体回收时机还是要看垃圾收集策略)。
代码示例
1.1 测试类
import java.util.concurrent.TimeUnit;
/**
* <p>
* 引用测试使用对象
* </p>
*
*/
public class ReferenceObject {
@Override
protected void finalize() throws Throwable {
//对象被回收前进行一个遗言的打印
System.out.println("哎呀!我" + this + "要被回收了!");
}
}
1.2 强引用测试
/**
* <p>
* 强引用测试
* </p>
*
*/
public class StrongReferenceTest {
public static void main(String[] args) throws InterruptedException {
ReferenceObject referenceObject = new ReferenceObject();
System.err.println("before gc :" + referenceObject);
//手动触发GC
System.gc();
//等待一秒
TimeUnit.SECONDS.sleep(1);
System.err.println("after gc :" + referenceObject);
System.err.println("======================================================================================");
//手动设置为null
referenceObject = null;
//重新触发GC进行测试
System.gc();
//等待一秒
TimeUnit.SECONDS.sleep(1);
System.err.println("after gc :" + referenceObject);
}
}
2、软引用
软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。
对于只有软引用的对象来说:
(1)当系统内存充足时它 不会被回收
(2)当系统内存不足时它 会被回收
软引用通常用在对内存敏感的程序中,比如MyBatis等高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收
代码示例
import java.lang.ref.SoftReference;
import java.util.concurrent.TimeUnit;
/**
* <p>
* 软引用测试
* </p>
*
*/
public class SoftReferenceTest {
public static void main(String[] args) throws InterruptedException {
//内存足够时测试
//sufficientMemory();
//内存不足时测试,需要设置启动内存 -Xms10m -Xmx10m
notEnoughMemory();
}
/**
* 内存不足时场景
* 需要设置内存:-Xms10m -Xmx10m
*
* @throws InterruptedE3xception
*/
private static void notEnoughMemory() throws InterruptedException {
SoftReference<ReferenceObject> softReference = new SoftReference<>(new ReferenceObject());
System.err.println("before gc :" + softReference.get());
//进行一次GC
System.gc();
System.err.println("after gc内存够用 :" + softReference.get());
TimeUnit.SECONDS.sleep(1);
try {
//创建一个9M的对象进行内存占用
byte[] bytes = new byte[9 * 1024 * 1024];
} finally {
System.err.println("after gc内存不够 :" + softReference.get());
}
}
/**
* 内存充足场景模拟
*
* @throws InterruptedException
*/
private static void sufficientMemory() throws InterruptedException {
SoftReference<ReferenceObject> softReference = new SoftReference<>(new ReferenceObject());
System.err.println("before gc内存够用 :" + softReference.get());
//手动触发GC
System.gc();
//等待一秒
TimeUnit.SECONDS.sleep(1);
System.err.println("after gc内存够用 :" + softReference.get());
}
}
3、弱引用
弱引用需要用 java.lang.ref.WeakReference 类来实现,它比软引用的生存期更短。
对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。
代码示例
import java.lang.ref.WeakReference;
import java.util.concurrent.TimeUnit;
/**
* <p>
* 弱引用测试类
* </p>
*/
public class WeakReferenceTest {
public static void main(String[] args) throws InterruptedException {
WeakReference<ReferenceObject> weakReference = new WeakReference<>(new ReferenceObject());
System.err.println("before gc:" + weakReference.get());
//手动触发GC
System.gc();
//等待一秒
TimeUnit.SECONDS.sleep(1);
System.err.println("after gc:" + weakReference.get());
}
}
4、虚引用
虚引用需要 java.lang.PhantomReference 类来实现
顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。
如果一个对象仅持有虚引用,那么它就和没有任务引用一样,在任何时候都可能被垃圾回收器回收。
它不能单独的使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。
虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。 PhantomReference的get()方法总是返回null,因此无法访问对应的引用对象。
其意义在于:说明一个对象已经进入finalization阶段,可以被GC回收,用来实现比 finalization机制更灵活的回收操作。
换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* <p>
* 虚引用测试类
* </p>
*/
public class PhantomReferenceTest {
/**
* 设置启动内存 -Xms15m -Xmx15m
*/
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<ReferenceObject> referenceQueue = new ReferenceQueue<>();
PhantomReference<ReferenceObject> phantomReference = new PhantomReference(new ReferenceObject(), referenceQueue);
//进行打印
System.err.println(phantomReference.get());
List<byte[]> list = new ArrayList<>();
//启动一个线程创建对象进行内存占用
new Thread(() -> {
while (true) {
list.add(new byte[1 * 1024 * 1024]);
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (Exception e) {
throw new RuntimeException(e);
}
System.err.println(Thread.currentThread().getName() + ":" + phantomReference.get());
}
}, "t1").start();
//引用队列进行输出
new Thread(() -> {
while (true) {
Reference<? extends ReferenceObject> poll = referenceQueue.poll();
if (poll != null) {
System.out.println(Thread.currentThread().getName() + ":-------- 有虚对象进入了队列!");
}
}
}, "t2").start();
TimeUnit.SECONDS.sleep(10);
}
}
5、小结
5.1 强引用不会被回收(死了都要爱)
5.2 软引用内存不足时会被GC回收 (死道友不死贫道)
5.3 弱引用GC一定会被回收 (每次挨打都有你)
5.4 虚引用开发用不到,主要时对象被回收后的处理