Java中根据引用的内存敏感度和GC之间的关系把引用分成了四个级别:强、软、弱、虚
强引用
宁可OutOfMemory也不回收。看下面的代码:
// 用于占位
public class Holder {
private static final int MB = 1024 * 1024;
private byte[] holder;
public Holder() {
this.holder = new byte[1 * MB];
}
}
public class RefrenceLeaning {
public static void main(String[] args) {
/**
* 强引用,宁可内存溢出也不回收
* 只有当设置为power=null的时候才会在GC的时候被释放
*
* 这个引起的问题是,当使用List等有自由内存结构的数据结构时,需要对用过的手工设置为null
*/
Holder d1 = new Holder();
Holder d2 = new Holder();
Holder d3 = new Holder();
Holder d4 = new Holder();
Holder d5 = new Holder();
}
}
用VM参数:
-verbose:gc -Xms2m -Xmx2m -Xmn1m -XX:SurvivorRatio=8 -XX:+PrintGCDetails
来执行,会发现抛出内存溢出异常。要解决强引用可能带来的内存消耗,办法是使用之后设置为null。这种情况比较适用于使用有自己的内存结构的数据结构时:
比如如下的一个Queue:
class Queue{
private Object[] data = new Object[24];
int index = 0;
public void put(Object o){
data[index] = o;
index++;
}
public Object getOne(){
// 这里因为没有设置返回的Object为null,可能会造成内存泄露
return data[index--];
}
}
用刚才的Holder来解释一下就是这样子的:
public class RefrenceLeaning {
public static void main(String[] args) {
/**
* 强引用,宁可内存溢出也不回收
* 只有当设置为power=null的时候才会在GC的时候被释放
*
* 这个引起的问题是,当使用List等有自由内存结构的数据结构时,需要对用过的手工设置为null
*/
Holder d1 = new Holder();
d1 = null;
Holder d2 = new Holder();
d2 = null;
Holder d3 = new Holder();
Holder d4 = new Holder();
Holder d5 = new Holder();
}
}
这样就不会内存溢出了,说明会被回收
更好的办法是使用作用域来隐式的表示引用的失效,如下:
public class RefrenceLeaning {
public static void main(String[] args) {
/**
* 强引用,宁可内存溢出也不回收
* 只有当设置为power=null的时候才会在GC的时候被释放
*
* 这个引起的问题是,当使用List等有自由内存结构的数据结构时,需要对用过的手工设置为null
*/
{
Holder d1 = new Holder();
Holder d2 = new Holder();
Holder d3 = new Holder();
}
Holder d4 = new Holder();
Holder d5 = new Holder();
}
}
这样也不会内存溢出。
软引用
很有意思的类, 当一个对象只有软引用的时候,内存不足的时候会被GC掉。注意这个只有。如下:
public class ReferencePowser<T> {
private T t;
public ReferencePowser(T t) {
this.t = t;
}
}
public class RefrenceLeaning {
public static void main(String[] args) {
/**
* 用作对比, d1在作用域执行完之后是可以被回收的了,但是因为有
* ReferencePowser的存在,引用还是强的,所以不会被GC。
**/
ReferencePowser<Holder> rp1 = null;
{
Holder d1 = new Holder();
rp1 = new ReferencePowser<Holder>(d1);
}
Holder d2 = new Holder();
SoftReference<Holder> sr2 = new SoftReference<Holder>(d2);
Holder d3 = new Holder();
Holder d4 = new Holder();
Holder d5 = new Holder();
}
}
这里会出现内存溢出。 因为虽然d1在作用域中,但是还有ReferencePowser中的引用,因此仍然不能GC。 看软引用的情况:
public class RefrenceLeaning {
public static void main(String[] args) {
/**
* 软引用, 当一个对象只有软引用,内存不足的时候会被GC。
*
*/
SoftReference<Holder> sr1 = null;
{
Holder d1 = new Holder();
sr1 = new SoftReference<Holder>(d1);
}
Holder d2 = new Holder();
SoftReference<Holder> sr2 = new SoftReference<Holder>(d2);
System.gc();
try {
Thread.sleep(1000);
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("d1=" + sr1.get());
Holder d3 = new Holder();
Holder d4 = new Holder();
Holder d5 = new Holder();
System.out.println("d1=" + sr1.get());
System.out.println("d2=" + sr2.get());
}
}
将会打印如下:
d1=com.price.effective.create.Holder@ca0b6
d1=null
d2=com.price.effective.create.Holder@1b67f74
可见之照样被回收了。 这样就可以实现一些特别的场景。 比如如果这个对象一直内存还够,就不会回收,可以用sr1.get()拿回来,但是内存不够的时候可能需要重新创建。这样可以一定程度的起到缓存的作用。
弱引用
跟软引用的区别在于,只有软引用的时候,只要执行了GC都会被GC掉,不会等到内存不足
public class RefrenceLeaning {
public static void main(String[] args) {
/**
* 弱引用,只有弱引用的时候,只要进行GC,就会被回收,不会等到内存不够的时候
*
*/
WeakReference<Holder> wr1 = null;
{
Holder d1 = new Holder();
wr1 = new WeakReference<Holder>(d1);
}
Holder d2 = new Holder();
SoftReference<Holder> sr2 = new SoftReference<Holder>(d2);
System.gc();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("d1=" + wr1.get());
Holder d3 = new Holder();
Holder d4 = new Holder();
Holder d5 = new Holder();
System.out.println("d1=" + wr1.get());
System.out.println("d2=" + sr2.get());
}
}
打印如下:
d1=null
d1=null
d2=com.price.effective.create.Holder@a62fc3
可以看出来,第一次GC的时候内存还够,但是已经被GC掉了。
虚引用
又叫幽灵引用,很少用到,只有虚引用的时候垃圾回收的时候是根本不会考虑这个引用的,该干啥干啥。
一般跟ReferenceQueue一起实现细粒度的内存控制。 当虚引用的对象GC后,这个虚引用会被放入到queue中,这样可以监控这个queue做一些特别的事情。从网上找到的一段代码很好。用来判断只有当内存回收之后才分配新的对象:
http://blog.youkuaiyun.com/imzoer/article/details/8044900