Java引用类型
强引用
csharp
public class M {
@Override
protected void finalize() throws Throwable {
System.out.println("finalize");
}
}
Object的finalize()方法是对象被回收时被调用的方法。一般来说不要去修改这个方法,执行你的代码可能会导致垃圾回收变慢,进而引发OOM的风险。
java
public class NormalReference {
public static void main(String[] args) throws IOException {
M m = new M();
m = null;
System.gc(); //DisableExplicitGC
System.out.println(m);
System.in.read(); // 阻塞main线程,给垃圾回收线程时间执行
}
}
输出结果:说明gc会在何时的时间回收没有引用的对象
csharp
null
finalize
软引用
软引用适合用来做缓存
。
csharp
public class T02_SoftReference {
// 要做这个实验,需要把设置 -Xmx=20M 在idea中可以设置VM options = -Xmx20M
public static void main(String[] args) {
SoftReference<byte[]> sr = new SoftReference<>(new byte[1024 * 1024 * 10]);// 1024 * 1024 = 1M
System.out.println(sr.get()); // 拿得到,没有回收
System.gc();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sr.get()); // 拿得到,没有回收
// 再分配一个数组,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用干掉
byte[] b = new byte[1024 * 1024 * 12];
System.out.println(sr.get()); // 拿不到,回收了
}
}
虚引用
虚引用的应用场景:
虚引用的数据永远拿不到,只有在回收的时候,把对象放进ReferenceQueue中。
csharp
public class T04_PhantomReference {
private static final List<Object> LIST = new LinkedList<>();
private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();
public static void main(String[] args) throws InterruptedException {
PhantomReference<M> phantomReference = new PhantomReference<>(new M(), QUEUE);
System.out.println(phantomReference.get()); // 虚引用的数据永远拿不到
ByteBuffer b = ByteBuffer.allocateDirect(1024);
new Thread(() -> {
while (true) {
LIST.add(new byte[1024 * 1024]);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(phantomReference.get());
}
}).start();
new Thread(() -> {
while (true) {
Reference<? extends M> poll = QUEUE.poll();
if (poll != null) {
System.out.println("--- 虚引用对象被jvm回收了 ----" + poll.get());
}
}
}).start();
Thread.sleep(1);
}
}
直接内存的使用方式:(这里面就包含了虚引用的使用)
arduino
public class TestDirectByteBuffer {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
}
弱引用
csharp
public class T03_WeakReference {
public static void main(String[] args) throws InterruptedException {
WeakReference<M> wr = new WeakReference<>(new M());
System.out.println(wr.get()); // 能拿到
System.gc();
Thread.sleep(1); // 睡1s让gc回收完
System.out.println(wr.get()); // 拿不到,我们并没有把wr=null,但是System.gc()方法执行后直接就回收了,垃圾回收器看到弱引用就直接当垃圾回收了
}
}
输出结果:
kotlin
com.example.code.gc.M@4d7e1886
finalize
null
垃圾回收器看到弱引用就当垃圾看直接回收。
Q:那有这个跟没有这个引用有什么区别?不都是直接被gc回收吗?
A:经典应用场景就是ThreadLocal。WeakHashMap。
csharp
public class ThreadLocal2 {
static ThreadLocal<Person> tl = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(() -> {
try {
Thread.sleep(1);
// 第一个线程设置对象
tl.set(new Person("zhangsan"));
System.out.println(tl.get());
// tl.remove();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
new Thread(() -> {
try {
Thread.sleep(2);
// 第二个线程获取对象,拿不到,即使都是操作的同一个对象tl
System.out.println(tl.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
}
static class Person{
public Person(String name) {
this.name = name;
}
String name = "zhangsan";
}
}
输出结果:同一个tl对象,都执行get方法,一个能拿到,一个拿不到
kotlin
com.example.code.gc.ThreadLocal2$Person@1b9c704e
null
ThreadLocal
跟线程挂钩
拿到的是当前线程池的成员变量
Thread销毁的时候,ThreadLocalMap也被回收了,但是很多Thread是不会被回收的,比如说线程池里面的核心线程。要避免内存泄漏,这里entry的key设置为虚引用,外面的tl被设置为null就能保证key会被回收了。但是还是会有内存泄漏的风险。key为null了,value就访问不到。value是一个强引用。这种情况最好还是要调用tl.remove()方法进行回收。才能保证内存不会泄漏。