一 强引用
我们平时使用的大多数对象引用,都是强引用,如下obj就是一个强引用,强引用的特点是:如果所有的GC ROOTS对象都没有与某个对象有强引用路径,那么这个对象就可以被垃圾回收了。
Object obj=new Object();
二 软引用
通过SoftReference引用对象时,这个引用就是软引用。软引用的特点是:如果一个对象仅存在软引用,当进行GC后,如果内存依旧不够使用,则在下次进行GC时,回收软引用引用的对象。软引用自身也是一个对象(SoftReference类型的对象),也会占用内存空间,SoftReference对象的回收需要使用引用队列。
package com.tech.gc.reference;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
/**
* 演示软引用
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
* @author lw
* @since 2021/11/4
*/
public class Demo_1 {
private static final int _4Mb=4*1024*1024;
public static void main(String[] args) throws IOException {
// List<byte[]> list=new ArrayList<>();
// for (int i = 0; i < 5; i++) {
// list.add(new byte[_4Mb]);
// }
// System.in.read();
soft();
}
public static void soft(){
// list-->SoftReference-->byte[]
List<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[_4Mb]);
System.out.println(softReference.get());
list.add(softReference);
System.out.println(list.size());
}
System.out.println("循环结束:"+list.size());
for (int i = 0; i < list.size(); i++) {
SoftReference<byte[]> softReference = list.get(i);
System.out.println(softReference.get());
}
}
}
分配了20M的堆内存空间,往list中添加4M的内存数组,添加5次,如果不使用软引用的方式,会出现内存溢出错误。
使用软引用的方式,日志如下:
"C:\Program Files\Java\jdk1.8.0_251\bin\java.exe" -Xmx20m -XX:+PrintGCDetails -verbose:gc "-javaagent:D:\Tech\resource\IntelliJ IDEA 2018.3.1\lib\idea_rt.jar=57163:D:\Tech\resource\IntelliJ IDEA 2018.3.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_251\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\rt.jar;D:\Tech\code\tech-jvm\target\classes;C:\Users\lw\.m2\repository\org\projectlombok\lombok\1.18.22\lombok-1.18.22.jar;C:\Users\lw\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.12.4\jackson-annotations-2.12.4.jar;C:\Users\lw\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.12.4\jackson-core-2.12.4.jar;C:\Users\lw\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.12.4\jackson-databind-2.12.4.jar" com.tech.gc.reference.Demo_1
[B@135fbaa4
1
[B@45ee12a7
2
[B@330bedb4
3
[GC (Allocation Failure) [PSYoungGen: 1995K->488K(6144K)] 14283K->12999K(19968K), 0.0009764 secs] [Times: user=0.05 sys=0.01, real=0.00 secs]
[B@2503dbd3
4
[GC (Allocation Failure) --[PSYoungGen: 4696K->4696K(6144K)] 17208K->17224K(19968K), 0.0008141 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 4696K->4579K(6144K)] [ParOldGen: 12527K->12442K(13824K)] 17224K->17021K(19968K), [Metaspace: 3275K->3275K(1056768K)], 0.0046699 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) --[PSYoungGen: 4579K->4579K(6144K)] 17021K->17069K(19968K), 0.0007473 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 4579K->0K(6144K)] [ParOldGen: 12490K->619K(8704K)] 17069K->619K(14848K), [Metaspace: 3275K->3275K(1056768K)], 0.0061145 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[B@4b67cf4d
5
循环结束:5
null
null
null
null
[B@4b67cf4d
Heap
PSYoungGen total 6144K, used 4377K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
eden space 5632K, 77% used [0x00000000ff980000,0x00000000ffdc66b8,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 8704K, used 619K [0x00000000fec00000, 0x00000000ff480000, 0x00000000ff980000)
object space 8704K, 7% used [0x00000000fec00000,0x00000000fec9ae98,0x00000000ff480000)
Metaspace used 3283K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 357K, capacity 388K, committed 512K, reserved 1048576K
Process finished with exit code 0
在添加第4个元素时,内存分配失败,触发GC(minor gc),回收了一部分空间,第4个元素添加成功;
在添加第5个元素时,内存分配失败,触发GC(minor gc),没有回收到空间,又触发了一次Full GC,几乎没有回收到空间,又触发一次GC(minor gc),没有回收到空间,由触发了一次Full GC,回收了大部分空间,根据打印的日志可以知道应该是前4个软引用对象被回收了。第5个元素添加成功。
所以最后打印日志:前面4个元素为null,最后一个不是null。
我们在list中存入的是软引用对象(SoftReference对象),软引用对象通过软引用引用着byte[]数组,我们想让byte[]回收时SoftReference也被回收,可以通过如下方式实现:
package com.tech.gc.reference;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
/**
* 演示软引用 引用对象(SoftReference对象)被清理
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
* @author lw
* @since 2021/11/4
*/
public class Demo_2 {
private static final int _4Mb=4*1024*1024;
public static void main(String[] args) throws IOException {
// List<byte[]> list=new ArrayList<>();
// for (int i = 0; i < 5; i++) {
// list.add(new byte[_4Mb]);
// System.out.println(list.size());
// }
// System.in.read();
soft();
}
public static void soft(){
// list-->SoftReference-->byte[]
ReferenceQueue<byte[]> referenceQueue=new ReferenceQueue<>();
List<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
//当软引用引用的对象,这里是byte[]被回收时,软引用自身(SoftReference对象)会被添加到引用队列ReferenceQueue中
SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[_4Mb],referenceQueue);
System.out.println(softReference.get());
list.add(softReference);
System.out.println(list.size());
}
System.out.println("循环结束:"+list.size());
//只要出现在引用队列中,说明这个软引用引用的对象正在被垃圾回收
//清楚list中已经被回收的软引用(SoftReference对象)
Reference<? extends byte[]> reference = referenceQueue.poll();
while (reference!=null){
list.remove(reference);
reference=referenceQueue.poll();
}
for (int i = 0; i < list.size(); i++) {
SoftReference<byte[]> softReference = list.get(i);
System.out.println(softReference.get());
}
}
}
通过运行结果可以知道,list只是强引用了1个SoftReference,其他的SoftReference由于没有强引用也会被回收。
三 弱引用
通过WeakReference引用对象,这种引用方式为弱引用。它的特点是:在进行GC时,无论内存是否充足,都会回收弱引用对象。弱引用本身(WeakReference对象)也会占用内存,它的回收需要使用引用队列。
弱引用的使用如下:
package com.tech.gc.reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* 演示弱引用
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
* @author lw
* @since 2021/11/4
*/
public class Demo_3 {
private static final int _4MB=4*1024*1024;
public static void main(String[] args) {
// list-->WeakReference-->byte[]
List<WeakReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[_4MB]);
list.add(weakReference);
for(WeakReference<byte[]> wrf:list){
System.out.print(wrf.get()+" ");
}
System.out.println();
}
System.out.println("循环结束:"+list.size());
}
}
四 虚引用
使用PlantomReference引用对象时,这种引用方式就是虚引用,虚引用需要结合引用队列ReferenceQueue使用,比如NIO中的ByteBuffer就使用到了虚引用来实现在ByteBuffer对象GC时,释放它申请的直接内存。它使用到了Cleaner对象,它继承于PlantomReference也是一个虚引用,在ByteBuffer通过Unsafe申请直接内存后,会创建Container对象,并把内存地址传递给Container对象,Contanner对象通过虚引用引用了ByteBuffer对象,在ByteBuffer对象被GC时虚引用Container对象会进入引用队列,在引用队列中有一个ReferenceHandler线程会定时扫描引用队列,发现Container对象就会调用它的相关方法,通过Unsafe释放直接内存。
五 终结器引用
使用FinalReference引用对象,这种引用方式为终结器引用。需要结合引用队列来使用。finalize方法的调用就使用到了终结器引用。当一个对象正在被回收时(还没有回收),虚拟机会创建该对象的终结器引用,并且进入引用队列,在引用队列上有一个优先级很低的线程,扫描到终结器引用,会根据引用找到对象,调用对象的finalize方法,实现资源清理,在下次GC时该对象会被回收。由于使用finalize的对象在被回收时,需要两次,且调用finalize的线程优先级特别低,finalize方法可能迟迟不能调用,所以不推荐使用finalize方法。