强引用
- 在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到 JVM 也不会回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。因此强引用是造成 Java 内存泄漏的主要原因之 一。
案例演示
public class Obj {
/**
* 在GC垃圾回收时被调用
*/
@Override
protected void finalize() throws Throwable {
System.out.println("==>> finalize 被打印,说明该对象被回收");
}
}
/**
* 强引用
*/
public class T01NormalReference {
public static void main(String[] args) throws IOException {
Obj o = new Obj();
o = null;
System.gc(); // DisableExplicitGC
System.in.read();
}
}
// 结果输出
// ==>> finalize 被打印,说明该对象被回收
软引用
- 软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中,用来实现内存敏感的缓存。。
案例演示
- 设置运行的最大堆内存
- 演示1
/**
* 软引用
*/
public class T02SoftReference {
private static final int SIZE_5_M = 1024 * 1024 * 5;
private static final int SIZE_6_M = 1024 * 1024 * 6;
public static void main(String[] args) {
SoftReference<byte[]> sr = new SoftReference<>(new byte[SIZE_5_M]);
System.out.println(sr.get());
System.gc();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println(sr.get());
// 再分配一个数组,堆内存将装不下,这时候系统会发生GC
// 先回收一次,如果不够,会把软引用干掉
byte[] b = new byte[SIZE_6_M];
System.out.println(sr.get());
}
}
- 结果输出
[B@61bbe9ba
[B@61bbe9ba
null // 说明被垃圾回收了
- 演示2
public class Obj {
private Integer id;
private String name;
public Obj() {
}
public Obj(Integer id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "[id=" + id + ", name=" + name + "]";
}
/**
* 在GC垃圾回收时被调用
*
* @throws Throwable
*/
@Override
protected void finalize() throws Throwable {
System.out.println("==>> finalize 被打印,说明该对象" + toString() + "被回收");
}
}
/**
* 软引用
*/
public class T02SoftReference {
public static void main(String[] args) {
SoftReference<Obj> sr = new SoftReference<>(new Obj(1, "Tom"));
Runnable r1 = () -> {
while (true) {
if (sr.get() == null) {
System.out.println("==>> " + sr.get());
break;
} else {
System.out.println("==>> " + sr.get());
}
}
};
Runnable r2 = () -> {
List<String> list = new ArrayList<>();
int i = 100;
while (true) {
list.add((i++) + "");
}
};
new Thread(r1).start();
new Thread(r2).start();
}
}
- 结果输出
...
==>> [id=1, name=Tom]
==>> [id=1, name=Tom]
==>> [id=1, name=Tom]
==>> [id=1, name=Tom]
==>> null
==>> finalize 被打印,说明该对象[id=1, name=Tom]被回收
Exception in thread "Thread-1" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.Arrays.copyOfRange(Arrays.java:3664)
at java.lang.String.<init>(String.java:207)
at java.lang.StringBuilder.toString(StringBuilder.java:407)
at com.yw.T02SoftReference.lambda$main$1(T02SoftReference.java:49)
at com.yw.T02SoftReference$$Lambda$2/2129789493.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
小结
- 当发生GC时,虚拟机可能会回收SoftReference对象所指向的软引用,是否被回收取决于该软引用是否是新创建或近期使用过。
- 在虚拟机抛出OutOfMemoryError之前,所有软引用对象都会被回收。
- 只要一个软引用对象由一个强引用指向,那么即使是OutOfMemoryError时,也不会被回收。
软引用主要用在缓存,比如你想使用一张大的图片。如果将这张图片用强引用存在内存里面,那么会比较占地方,其它线程都不能使用;但如果不存起来,每次都要去进行加载,效率会很低。这个时候,你就可以使用软引用,在内存充足时可以缓存,在内存不足时又可以及时把内存空间释放,兼顾了时间和空间的效率。
弱引用
- 弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。
案例演示
/**
* 弱引用
*/
public class T03WeakReference {
public static void main(String[] args) {
WeakReference<Obj> wr = new WeakReference<>(new Obj());
System.out.println(wr.get());
System.gc();
System.out.println(wr.get());
ThreadLocal<Obj> tl = new ThreadLocal<>();
tl.set(new Obj());
tl.remove(); // !!! 注意用完了要remove,防止内存泄漏
}
}
虚引用
- 虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态,应用于对堆外内存的管理。
案例演示
public class T04PhantomReference {
private static final List<Object> LIST = new LinkedList<>();
private static final ReferenceQueue<Obj> QUEUE = new ReferenceQueue<>();
public static void main(String[] args) {
PhantomReference<Obj> pr = new PhantomReference<>(new Obj(), QUEUE);
new Thread(() -> {
while (true) {
LIST.add(new byte[1024 * 1024]);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
Thread.currentThread().interrupt();
}
System.out.println(pr.get());
}
}).start();
new Thread(() -> {
while (true) {
Reference<? extends Obj> poll = QUEUE.poll();
if (poll != null) {
System.out.println("--- 虚引用对象被JVM回收了 ---" + poll);
}
}
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
- 结果输出:
null
null
==>> finalize 被打印,说明该对象[id=null, name=null]被回收
null
null
null
...