1、Java中的引用类型
1.1 强引用
Normal Reference【正常的引用】
当内存中没有东西指向它,就会被回收。
- 重写T中的finalize()方法【对象实例被回收时会调用finalize()方法,为了能显式看到gc】
public class T {
@Override
protected void finalize() throws Throwable {
System.out.println("finalize");
}
}
- 强引用对象,当没有引用指向它时,才会被回收
//强引用
public class NormalReference {
public static void main(String[] args) throws IOException {
T t=new T(); //建立一个强引用
t=null; //去除强引用
System.gc(); //显式调用垃圾回收
System.in.read();
}
}
为什么最后要写System.in.read()呢?
因为如果不写这句代码的话,main方法退出了,垃圾回收是在单独的垃圾回收线程中,而不是在main线程中。有可能垃圾回收线程还没有进行垃圾回收,main线程结束,那就不能显式的看到垃圾回收的输出结果了。
1.2 软引用
Soft Reference【软引用本身首先是一个对象,再指向它所引用的对象。软引用对象实例本身都存放在堆内存中,它所引用的目标对象也是存放在堆内存中。】
内存示意图大致如下
SoftReference<byte []> m=new SoftReference<>(new byte[1024*1024*10]);
当堆内存空间不足时,先会进行一次垃圾回收,堆内存还不够,就会回收被软引用指向对象。
- 举例子
-
先通过-Xmx设置JVM运行时可申请的最大堆内存为20MB
-
先建立软引用指向10MB的字节数组,再建立强引用指向10MB的字节数组。JVM会先垃圾回收一次,堆内存还是不够,就会回收软引用对象。因此第三次软引用m.get()输出为null
//软引用
public class T02_SoftReference {
//设置 jvm堆内存参数为20MB
public static void main(String[] args) throws InterruptedException {
//虚引用指向对象 占10MB
SoftReference<byte []> m=new SoftReference<>(new byte[1024*1024*10]);
System.out.println(m.get());
//堆内存存在空间不会被回收
System.gc();
Thread.sleep(500);
//堆内存存在空间不会被回收,此时软引用仍存在
System.out.println(m.get());
//再在堆中分配一个数组,堆中装不下,先发生一次垃圾回收
// 堆内存还不够,就会回收软引用对象
byte[] b=new byte[1024*1024*10];
System.out.println(m.get());
}
}
1.3 弱引用
WeakReference【弱引用和软引用类似,本身都是一个对象,并且指向它们所引用的对象】
WeakReference<byte []> m=new WeakReference<>(new byte[1024*1024*10]);
内存示意图大致如下
只要发生垃圾回收,就会回收被弱引用指向的对象。
使用场景:解决在Map的Entry内存管理中,可能会出现的内存泄漏问题。
- 举例子
//弱引用
public class T03_WeakReference {
public static void main(String[] args) {
WeakReference<T> wr=new WeakReference<>(new T());
System.out.println(wr.get());
System.gc();
System.out.println(wr.get());
}
}
1.4 虚引用
虚引用指向对象被回收的时候,会把信息填到Queue中去。【当某个对象被回收时,我能知道它被回收了。】我们使用get()方法是获取不到的。
- 举例子
package Reference;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
public class T04_PhantomReference {
private static final List<Object> list=new LinkedList<>();
private static final ReferenceQueue<T> QUEUE=new ReferenceQueue<>();
public static void main(String[] args) {
PhantomReference<T> phantomReference=new PhantomReference<>(new T(),QUEUE);
new Thread(()->{
while (true){
list.add(new byte[1024][1024]);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.println(phantomReference.get()); //一直是null
}
}).start();
new Thread(()->{
while (true){
Reference<? extends T> poll = QUEUE.poll();
if (poll!=null){
System.out.println("--虚引用对象被JVM回收了--");
}
}
}).start();
}
}
- 回收的消息被填充到QUEUE中
使用场景:(使用get都get不到,用它干嘛?)管理堆外内存
JVM只占了操作系统中的一部分内存,当我们从网络中读数据时,操作系统首先读到操作系统内存中,然后再拷贝到JVM内存中,这样效率特别低,因此JVM提供了一种直接内存的方法,直接去访问操作系统的内存,但是不是JVM自己管理的内存,JVM是不知道也没有办法进行垃圾回收的。
因此,我们建立一个DirectByteBuffer虚引用,指向堆外内存。当这个虚引用对象被回收之后,可以通过QUEUE检测到,然后JVM清理对外内存。
2、ThreadLocal
2.1 概念
ThreadLocal可以为每一个线程提供自己专属的本地变量。
2.3 用法
- 创建ThreadLocal对象,并绑定需要提供的本地变量
static ThreadLocal<Person> tl=new ThreadLocal<Person>();
- 各个变量通过ThreadLocal的set、get方法来设置和获取本地变量
2.3 内存泄漏相关概念
- 内存溢出: Memory overflow 没有足够的内存提供申请者使用。
- 内存泄漏: Memory Leak 程序中已经动态分配的堆内存由于某种原因, 程序未释放或者无法释放, 造成系统内部的浪费, 导致程序运行速度减缓甚至系统崩溃等严重结果. 内存泄漏的堆积终将导致内存溢出。
2.4 原理【源码解读】
-
Thread类
Thread类中有一个ThreadLocalMap对象 threadLocals
-
ThreadLocal类
ThreadLocal类中有一个静态类ThreadLocalMap,ThreadLocalMap中的Entry继承了弱引用,Entry中的key为虚引用指向当前ThreadLocal变量,value为我们set的值。
-
举例子
public class T05_ThreadLocal {
static ThreadLocal<Person> tl=new ThreadLocal<Person>();
public static void main(String[] args) {
//Thread1
new Thread(()->{
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(tl.get());
}).start();
//Thread2
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
tl.set(new Person(12));
});
}
}
- Thread2会设置自己的ThreadLocalMap,因此Thread1调用get()方法为空
注意事项:当tl=null后,意为解除了ThreadLocal的强引用,若key指向当前ThreadLocal为强引用,这个ThreadLocal就不会被回收,会发生内存泄漏。如果这里是弱引用,只要遇到垃圾回收,key所指向的ThreadLocal就会被回收。
注意事项2:key被回收后,此时entry变为(null,value),因此还要使用tl.remove()方法,回收ThreadLocalMap中的Entry。
- 反正都要使用remove()回收entry,强引用和弱引用不是一样的吗?
弱引用多一层保护机制,当这个线程执行完后忘记remove,entry有的是会被回收掉的。比如ThreadLocalMap在调用set,get,remove方法时都会清理 key 为 null 的entry。强引用忘记remove,entry永远都不会回收掉。