五大引用概述
Java技术允许使用finalize()
方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
官方API:finalize()
的通常目的是在对象被不可撤销地丢弃之前执行清理操作。
- 但是我们不建议去重写finalize方法,会在终结器引用进行详细介绍
基于可达性算法的垃圾回收
- 实线是强引用,虚线是其他引用
什么是GC Root
可以作为GC Root对象
- 虚拟机栈(栈帧中的本地变量表)中引用的对象,比如当前正在运行的方法所使用到的参数,局部变量,临时变量
- 方法区中类静态属性引用的对象,比如Java类的引用类型静态变量
- 方法区中常量引用的对象,比如字符串常量池(StringTable)中的引用
- 本地方法栈中JNI(一般说的Native方法)引用的对象
- Java虚拟机内部的引用,如基本数据类型对应的Class文件,一些常驻的异常对象等,还有系统类加载器
- 所有被同步锁(synchronized关键字)持有的对象
- 反应Java虚拟机内部情况的JMXBean,JVMMTI中注册的回调,本地方法缓存等
强引用
- 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
- 我们平时用new出来的对象,用一个引用来指向这个对象,这个引用是强引用
演示
class MyObject{
@Override
protected void finalize() throws Throwable{
//finalize的通常目的是在对象被不可撤销的丢弃之前进行清理操作
System.out.println("finalize()被调用-------invoke finalize");
}
}
public class ReferenceDemo {
public static void main(String[] args) {
MyObject myObject = new MyObject();
System.out.println("gc before"+myObject);
myObject=null;
System.gc();
System.out.println("gc after "+myObject);
}
}
gc beforecom.lsc.day07.MyObject@14ae5a5
gc after null
finalize()被调用-------invoke finalize //说明myObject被回收了
软引用
软引用用来描述一些还有用,但非必须的对象
- 通过GC Root对象强引用了我们的软引用对象,然后用软引用对象指向一个对象,这个对象就是被软引用指向的对象
- 软引用自身也是会占内存的
- 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象,可以配合引用队列来释放软引用自身
- 软引用是在回收了一次,内存还是不够,进行第二次垃圾回收才回收
演示
/**
* 演示软引用
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
*/
public class Demo2_3 {
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();
}
//这是我们的软引用
public static void soft() {
// list --> SoftReference --> byte[]
List<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());
}
System.out.println("循环结束:" + list.size());
for (SoftReference<byte[]> ref : list) {
System.out.println(ref.get());
}
}
}
强引用执行的结果
- 会发生推内存溢出的现象,因为都是强引用,所以垃圾回收无法回收
软引用执行的结果
- 在第五次进行分配空间的时候,内存不够了,第一次进程垃圾回收发现内存还是不够,所以触发第二次的垃圾回收对软引用对象进行回收,所以发现前四个byte数组的内存地址为null
演示配合引用队列对软引用本身进行回收
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
/**
* 演示软引用, 配合引用队列
*/
public class Demo2_4 {
private static final int _4MB = 4 * 1024 * 1024;
public static void main(String[] args) {
List<SoftReference<byte[]>> list = new ArrayList<>();
// 引用队列
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
for (int i = 0; i < 5; i++) {
// 关联了引用队列, 当软引用所关联的 byte[]被回收时,软引用自己会加入到 queue 中去
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());
}
// 从队列中获取无用的 软引用对象,并移除
Reference<? extends byte[]> poll = queue.poll();
while( poll != null) {
list.remove(poll);
poll = queue.poll();
}
System.out.println("===========================");
for (SoftReference<byte[]> reference : list) {
System.out.println(reference.get());
}
}
}
- queue队列就是我们的引用队列,当我软引用所关联的byte数组对象被回收,软引用就会加到queue中
弱引用
弱引用的功能跟软引用一样,只是强度比软引用还弱
- 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象,可以配合引用队列来释放弱引用自身
演示
public class Code_09_WeakReferenceTest {
public static void main(String[] args) {
// method1();
method2();
}
public static int _4MB = 4 * 1024 *1024;
// 演示 弱引用
public static void method1() {
List<WeakReference<byte[]>> list = new ArrayList<>();
for(int i = 0; i < 10; i++) {
WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB]);
list.add(weakReference);
for(WeakReference<byte[]> wake : list) {
System.out.print(wake.get() + ",");
}
System.out.println();
}
}
// 演示 弱引用搭配 引用队列
public static void method2() {
List<WeakReference<byte[]>> list = new ArrayList<>();
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
for(int i = 0; i < 9; i++) {
WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB], queue);
list.add(weakReference);
for(WeakReference<byte[]> wake : list) {
System.out.print(wake.get() + ",");
}
System.out.println();
}
System.out.println("===========================================");
Reference<? extends byte[]> poll = queue.poll();
while (poll != null) {
list.remove(poll);
poll = queue.poll();
}
for(WeakReference<byte[]> wake : list) {
System.out.print(wake.get() + ",");
}
}
}
弱引用执行的结果
虚引用
虚引用有个最重要的应用就是我们的直接内存分配的ByteBuffer
-
虚引用必须和引用队列 (ReferenceQueue)联合使用,在上的ByteBuffer对象中的cleaner对象就是我们的虚引用,存储的是我们的直接内存的地址,然后我们的ReferenceHandler线程调用cleaner对象的clean方法,然后调用了unsafe.freeMemory来释放我们的直接内存
-
因为直接内存不受JVM的内存自动管理的控制,所以通过这些方法来间接的实现对直接内存的回收
-
虚引用的目的
- 一个对象是否有虚引用完全不会对其生存期间构成影响,也无法通过虚引用来取得一个对象的实例
- 设置我们虚引用的目的就是为了能在这个对象被回收时系统能收到一个系统通知
class MyObject{
@Override
protected void finalize() throws Throwable{
//finalize的通常目的是在对象被不可撤销的丢弃之前进行清理操作
System.out.println("finalize()被调用-------invoke finalize");
}
}
public class demo {
public static void main(String[] args) {
MyObject myObject = new MyObject();
ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();
PhantomReference<MyObject> phantomReference = new PhantomReference<>(myObject, referenceQueue);
// System.out.println(phantomReference.get());//这里就是个null--虚引用的get()就是null
List<byte[]> list = new ArrayList<>();
new Thread(() -> {
while (true)//模拟一个无限循环
{
try {
list.add(new byte[1 * 1024 * 1024]);
try { TimeUnit.MILLISECONDS.sleep(600); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(phantomReference.get());
}catch (OutOfMemoryError e){
try {
Thread.sleep(300);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
}
},"t1").start();
new Thread(() -> {
while (true)
{
Reference<? extends MyObject> reference = referenceQueue.poll();
if (reference != null) {
System.out.println("有虚对象加入队列了");
}
}
},"t2").start();
}
}
null
null
null
null
null
null
null
finalize()被调用-------invoke finalize
有虚对象加入队列了
-
虚引用需要java.lang.ret.PhantomReterence类来实现,顾名思义, 就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。
-
PhantomReference的get方法总是返回null,虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一和确保对象被 finalize以后,做某些事情的通知机制。PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。
-
处理监控通知使用换句话说,设置虚引用关联对象的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理,用来实现比finalize机制更灵活的回收操作。
终结器引用
即使在可达性分析算法的时候被判为了不可达对象,也不是"非死不可",这时候是暂时处于缓刑阶段,这也是我们的终结器引用的原理
- finalize()方法是我们的Object中的方法,所以所有对象都有这个方法
- 对于我们真正宣告一个对象死亡,最多会经历两次标记过程
- 如果被判定为不可达对象,是第一次标记
- 随后对这些对象进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,如果没有覆写过finalize()方法,或者已经调用过了,就是被视为没有必须执行
- 重写了了finalize方法,A4对象第一次垃圾回收的时候并不是立马被回收,因为重写了finallize方法,所以虚拟机会自动创建一个终结器引用,执行A4对象的第一次回收的时候,会将终结器引用放入引用队列(不会回收A4对象)
- 由我们的Finalizer线程通过终结器引用找到被引用的对象,并调用他的finallize方法
- 如果我们的处于缓刑的对象在finalize()方法中被引用链上对象链上了,那么也就是被复活了,也就不会被垃圾回收
- 如果没复活,第二次GC时才会回收这个A4对象
- 不推荐使用
- 因为处理引用队列的线程优先级很低,被处理的机会很少,可能造成这个对象的finalize()迟迟不被调用,导致这块内存迟迟不被回收
- 而且这个方法的执行,并不保证一定会等待它执行完毕,因为如果执行缓慢,或者死循环,那么导致其他对象回收的等待