目录
目录
目录
0.前言:
1.强引用:当对象被一个或一个以上的引用类型变量引用时,此对象处于可达状态不会被回收。(当没有引用变量引用此对象时,对象就处于不可达状态。 就非常非常可能会被回收)
2.软引用:软引用需要用到SoftReference类,内存不够时,可能会被回收。
(通常用于内存敏感的程序中) 对于不是必须一直存在于内存中的对象可以使用软引用,比如处理一张图片的过程,如果更重要的程序需要在处理过程中途运行,可以将这张图片使用软引用。 (应用于缓存数据<硬盘中还有一份儿, 内存不够时可以清理这个缓存中的数据,因为硬盘中还有一份儿数据>)
(中途的程序在内存中存不下时,就删除这个软引用代表的对象)
3.弱引用:弱引用需要用到WeakReference类,无论内存够不够,只要垃圾回收器启动,弱引用关联的对象肯定被回收。用来描述非必需对象。(和软引用很像,但比软引用级别更低)。
常见的应用是ThreadLocal保存数据时key采用弱引用。
再比如: java集合中有一个WeakHashMap专门用来存储弱引用的(map中存储弱引用,弱引用指向一个对象)
WeakHashMap<String, Object> whm=new WeakHashMap<String, Object>();
4.虚引用:PhantomReference: 虚引用需要用到PhantomReference类, 用来在对象被回收时接收一个系统通知。
如果一个对象具有虚引用,那么它和没有任何引用一样,
被虚引用关联的对象引用通过get方法获取到的永远为null,
也就是说这种对象在任何时候都有可能被垃圾回收器回收,
通过这种方式关联的对象也无法调用对象中的方法。
虚引用主要是用来管理堆外内存的,通过ReferenceQueue这个类实现,当一个对象被回收的时候,会向这个引用队列里面添加相关数据,给一个通知。
设置虚引用的唯一目的,就是在这个对象被回收器回收的时候收到一个系统通知或者后续添加进一步的处理。
1.强引用:
最常见的一种对象引用方式: Test1 myObject = new Test1();
/**
*
* @author zhaoYQ
*
*
*/
public class Test1 {
@Test
public void strongReference() throws InterruptedException {
Test1 myObject = new Test1();
System.out.println("Gc前:" + myObject);
System.gc();
TimeUnit.SECONDS.sleep(1);
//强引用: 未置空无法被GC
System.out.println("未置空Gc后打印:" + myObject);
myObject = null; //置空可被GC
System.gc();
System.runFinalization();//强制调用finalize
TimeUnit.SECONDS.sleep(1);
System.out.println("置空后Gc后打印:" + myObject);//置空可被GC
}
protected void finalize() throws Throwable {
System.out.println("对象被回收");
super.finalize();
}
}
2.软引用:
package com;
import java.lang.ref.SoftReference;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
/**
*
* @author zhaoYQ
* 软引用
*
*/
public class Test1 {
@Test
public void strongReference() throws InterruptedException {
byte[] t1 = new byte[1024*1024*10];//10M
SoftReference<byte[]> softReference = new SoftReference<byte[]>(t1);
System.out.println("Gc前,打印对象:" + softReference.get());
t1=null;
System.gc();//将t1的引用置空//只存在软引用(不存在强引用)
TimeUnit.SECONDS.sleep(1);
System.out.println("Gc后,打印对象:" + softReference.get());
//再次创建一个数组,堆中存不下的时候,垃圾回收器工作
//先回收一次,如果第一次回收后内存还是不够
//则再清理第二次,这一次会把软引用对象清除
byte[] t2 = new byte[1024*1024*15];//15M
System.gc();//启动GC(但是GC不一定会回收某个对象<要看当时堆内存环境>)
TimeUnit.SECONDS.sleep(1);
System.out.println("Gc后,打印对象:" + softReference.get());
}
}
运行时设置堆内存大小(设置运行参数):
-Xmx100M 当堆最大内存设置为100M时因为内存够用,所以出发GC时,对象不会被回收。
如下,当当堆最大内存设置为25M时因为内存不够用, 所以当需要往堆中再次分配一个更大的t2时,就要回收t1了(所以软引用是当内存不够时才可能回收<内存空间够用时不会回收>)
3.弱引用:
package com;
import java.lang.ref.WeakReference;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
/*
System.gc(); 等效于: Runtime.getRuntime().gc()
//告诉垃圾收集器打算进行垃圾收集,而垃圾收集器进不进行收集是不确定的
System.runFinalization(); 等效于: Runtime.getRuntime().runFinalization()
//强制调用已经失去引用的对象的finalize方法
*/
/**
*
* @author zhaoYQ
*
*
*/
public class Test1 {
@Test
public void strongReference() throws InterruptedException {
// 1.创建一个对象
Test1 t1 = new Test1();// 强引用,未置空无法被GC
// 2.创建一个弱引用,让弱引用引用对象t1
WeakReference<Test1> wr = new WeakReference<Test1>(t1);
// 3.切断t1和对象之间的引用
t1 = null;
// 4.取出弱引用所指代的对象
System.out.println("gc前打印对象:"+wr.get());//打印对象
// 5.催促垃圾回收
System.gc();
TimeUnit.SECONDS.sleep(2);
System.runFinalization();//强制调用finalize
// 6.再次取出弱引用所指向的对象
System.out.println("gc后打印对象:"+wr.get());//null
}
protected void finalize() throws Throwable {
System.out.println("对象被回收");
super.finalize();
}
}
执行结果如下:
gc前打印对象:com.Test1@fbb2c1
对象被回收
gc后打印对象:null
弱引用说明案例2:
package com.zyq.csmall.business.config;
import java.lang.ref.WeakReference;
import java.util.concurrent.TimeUnit;
public class Test {
// 运行结果:
// weakRef = com.zyq.csmall.business.config.Test$1M@19f7e29
// M对象被回收
// weakRef = null
public static void main(String[] args) throws InterruptedException {
class M{
protected void finalize() throws Throwable {
System.out.println("M对象被回收");
}
}
WeakReference weakRef=new WeakReference(new M());
System.out.println("weakRef = " + weakRef.get());
System.gc();
TimeUnit.SECONDS.sleep(1);
System.out.println("weakRef = " + weakRef.get());
}
}
4.虚引用:
使用的情景非常少,不用掌握。
虚引用需要用到PhantomReference类, 用来在对象被回收时接收一个系统通知。
如果一个对象具有虚引用,那么它和没有任何引用一样,
被虚引用关联的对象引用通过get方法获取到的永远为null,
也就是说这种对象在任何时候都有可能被垃圾回收器回收,
通过这种方式关联的对象也无法调用对象中的方法。
虚引用主要是用来管理堆外内存的,通过ReferenceQueue这个类实现,当一个对象被回收的时候,会向这个引用队列里面添加相关数据,给一个通知。
设置虚引用的唯一目的,就是在这个对象被回收器回收的时候收到一个系统通知或者后续添加进一步的处理。
下边的情况下: 当堆中的对象被回收时,用来通知垃圾回收器将堆外的这个对象同时回收掉。
虚引用这这种情景下: 就可以在堆中的对象被回收时通知 垃圾回收器去回收堆外的这个字节缓冲数组 (因为堆中使用字节缓冲数组的对象已经要被回收了, 堆外的这个字节缓冲数组就没有存在的必要了)
案例: 作者用意没看出来
package com;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class Test {//这个给List中不停存入数组的情况体现不出虚引用,这个案例作者的用意没体会出来
//-verbose:gc -XX:+PrintGCDetails -XX:+DisableExplicitGC -XX:MaxDirectMemorySize=40M
// 禁止手动调用System.GC() 堆外最大内存是40M
private static final List<Object> LIST=new LinkedList<>();
private static final ReferenceQueue<Test> QUEUE=new ReferenceQueue<>();
public static void main(String[] args) {
//虚引用使用的时候里边必须给引用的对象对应一个QUEUE的队列
//用来在对象被回收之前(注意是回收之前),将把此对象对应的虚引用
//添加到它关联的引用队列QUEUE中(用来告知虚拟机此对象关联的堆外对象也应该被回收掉)
PhantomReference<Test> phantomRef=new PhantomReference<>(new Test(),QUEUE);
//注意: 虚引用和没有引用一样(使用虚引用无法获取所引用的对象)
System.out.println(phantomRef.get());//null
//ByteBuffer.allocate(1024)可以在堆外创建一个字节数组用来:
//在nio通信中直接接收网卡的数据(如果不在堆外创建此数组,则需要
//在堆中创建一个类似的数组来拷贝操作系统接收网卡后在堆外构建的
//一个字节数组的数据) (用ByteBuffer.allocate(1024)可以在堆外
//创建一个字节数组用来直接接收网卡的数据<尤其在游戏编程中经常使用>)
ByteBuffer b=ByteBuffer.allocate(1024);
Thread t1=new Thread(()->{
while(true){
try {
//往LIST集合每次存入一个大小为1M的数组(LIST集合会越来越大)
LIST.add(new byte[1*1024*1024]);
TimeUnit.SECONDS.sleep(1);
//注意: 虚引用和没有引用一样(使用虚引用无法获取所引用的对象)
System.out.println(phantomRef.get());//null
} catch (Exception e) {
e.printStackTrace();
}
}
});
t1.start();
}
}
5.面试题扩展:
你使用过ThreadLocal吗, ThreadLocal是否有可能会导致内存泄漏?
答案: A.ThreadLocal是某个线程给ThreadLocal中存入数据时,这个数据只能给这个线程用
(其他线程无法获取此数据) //放入的数据时引用类型的(对象)
B.ThreadLocal的数据使用完一定要调用ThreadLocal对象的remove方法将放入的数据
置为null, 这样垃圾回收器才会回收这个数据对象
案例扩展: ThreadLocal的使用方式如下:
package com.zyq.csmall.business.config;
import java.util.concurrent.TimeUnit;
public class TestTL {
/**
* ThreadLocal中有一个Map集合,可以给里边放入元素
* (特点是: 哪个线程给ThreadLocal放元素,哪个线程才能获取这个数值
* <其他线程获取不到>)
* 观察源码:ThreadLocal的set(T value)方法可以发现它从当前线程中会获取一个
* Map,然后给里边放入元素:
* Thread t = Thread.currentThread();
* ThreadLocalMap map = getMap(t);
*
* getMap(Thread t) { return t.threadLocals; }
* if (map != null) map.set(this, value);
* //当前线程中有一个ThreadLocalMap(就是一个Map),把当前ThreadLocal
* 的引用(ThreadLocal本身)作为key,数值作为value放入到ThreadLocalMap
* //所以如果Map中需要放入多个value,就得有多个key(多个ThreadLocal)
*
* 注意:
* 观察ThreadLocal的set(T value)方法中的map.set(this, value)源码,
* 发现:
* ThreadLocalMap是ThreadLocal的静态内部类
* 这个ThreadLocalMap的set方法放元素逻辑是:
* // map.set(this, value);
* ThreadLocalMap中封装了一个Entry数组,Entry键值对类继承了弱引用
* WeakReference类
* (键值对类在创建对象时,会先创建父类WeakReference对象:
* super(k)父类对象会用key指向)
* super(k)(所以ThreadLocalMap的每个元素的
* key<ThreadLocal对象还会被一个弱引用指向ThreadLocalMap对象)
* (ThreadLocal对象会被tL强引用指向, 还会被一个弱引用
* WeakReference指向)
*
*/
public static void main(String[] args) {
ThreadLocal<Integer> tL=new ThreadLocal<>();
//ThreadLocal<Integer> tl2=new ThreadLocal<>();
Thread th1=new Thread(){
public void run() {
tL.set(1);
//tl2.set(2);
System.out.println("Integer:"+tL.get());
//System.out.println("Integer:"+tl2.get());
//不用ThreadLocal之后一定要调用tL.remove()将Entry的value置为null
//(弱引用解决key, tL.remove()解决value)
//(弱引用指向的ThreadLocal对象在垃圾回收时会被回收掉, tL.remove会将
// Entry的value置为null<将value指向的对象设置为垃圾对象, GC会回收此对象>)
//否则会导致th1指向线程对象,线程对象指向ThreadLocalMap threadLocals
// threadLocals指向ThreadLoalMap对象,ThreadLoalMap对象指向Entry数组,
tL.remove();
}
};
th1.start();
Thread th2=new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("Integer:"+tL.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
th2.start();
}
}
6.线程池中的ThreadLocal:
扩展线程池的TheadLocal的remove()问题:
同样的 ,在线程池中使用ThreadLocal存储数据时,每次将线程使用完也要将此线程的ThreadLocalMap清空,否则下一条线程可能会获取上一条线程的数据,也会导致内存泄漏。
参考文章:
(266条消息) Java四大引用_不会叫的狼的博客-CSDN博客_java的四大引用
https://blog.csdn.net/weixin_40017062/article/details/124958000