一、强软弱虚
1、强引用:平时new出来的对象,只要有引用在,即使发生GC也回收不了。
public class M {
@Override
protected void finalize() throws Throwable {
System.out.println("finalize");
}
}
finalize()方法,当这个对象被回收时,会调用finalize()方法。
public class T01_NormalReference {
public static void main(String[] args) throws IOException {
M m = new M();
System.gc();
System.out.println(m);
m = null;
System.gc(); //DisableExplicitGC
System.out.println(m);
System.in.read();
}
}
执行main方法,结果如下: 可以看出第一次执行System.out.println(m);之后对象还在。证明了只要引用在,即使发生了GC也回收不了该对象。m=null,把引用断掉,此时发生GC,对象被回收掉了。
2、软引用:空间不够就回收,软引用适合做缓存,空间足够就放在那,空间不够就回收。
vm options: -Xms20M -Xmx20M 设置最大堆最小堆20M。
public class T02_SoftReference {
public static void main(String[] args) {
//10M
SoftReference m = new SoftReference<>(new byte[1024*1024*10]);
//m = null;
System.out.println(m.get());
System.gc();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(m.get());
//11M
//再分配一个数组,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用干掉
byte[] b = new byte[1024*1024*11];
System.out.println(m.get());
}
}
执行main方法,结果如下:最大堆,最小堆为20M,首先分配了一个10M的byte数组对象,然后执行gc操作,可以看到byte数组对象没有被回收,当尝试分配一个11M的数组时,heap装不下时,这时系统会垃圾收回,把软引用干掉。
引用关系如下:
3、弱引用:遇到GC就会回收,解决ThreadLocal内存泄漏的问题。
public class T03_WeakReference {
public static void main(String[] args) {
WeakReference m = new WeakReference<>(new M());
System.out.println(m.get());
System.gc();
System.out.println(m.get());
// ThreadLocal tl = new ThreadLocal<>();
// tl.set(new M());
// tl.remove();
}
}
执行main方法,结果如下:gc之后,垃圾被回收。
引用关系如下:
弱引用只要遭遇垃圾回收,就会被回收掉。可能有人认为这弱引用一GC就被回收,一GC就被收回,那还有啥用啊?如果有另外一个强引用指向大M的时候,只要这个强引用消失掉,大M就应该去被回收。一般用在容器。最典型的应用就是ThreadLocal。
ThreadLocal内存泄漏:
public class ThreadLocal2 {
static ThreadLocal tl = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
tl.set(new Person());
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tl.get());
}).start();
}
static class Person {
String name = "zhangsan";
}
}
先来看看Thread源码
可以发现,每一个Thread都会对应1个ThreadLocalMap成员变量(也称threadLocals),也就是说1个线程对应1个ThreadLocalMap。
再看看ThreadLocal中的源码
可以看出ThreadLocalMap类是ThreadLocal的静态内部类,而Entry是ThreadLocalMap的静态内部类。通过set方法可以看出,key为this,也就是ThreadLocal(entry中的key声明为弱引用),value是Object,也就是我们要存的值。
往ThreadLocalMap里设置的值是啥?和tl有啥关系?ThreadLocalMap每1个元素对应1个entry。1个entry包含一对key、value。key为 tl,value是person。t1=new ThreadLocal<>();此时是强引用
① 为什么Entry要使用弱引用?设置为弱引用的key能预防大多数内存泄漏的情况,若是强引用,即使t1=null,但key的引用仍然指向ThreadLocal对象,会导致entry内存泄漏。。如果key为
②为什么要remove?ThreadLocalMap的生命周期和当前线程一样长。ThreadLocal对象被回收,key的值变为null,则导致整个value再也无法被访问到。(也就是ThreadLocalMap中会存在key为null,但是value不为null)因此仍然存在内存泄漏。如果不用了,务必remove掉。若当前线程一直不结束或者作为线程池中的一员。重复使用线程(线程id可能不变,导致内存泄漏)
4、虚引用:管理堆外内存。