java中有四种引用类型,或者说是引用方式。
- 强引用(String Reference):即时进行了多次的GC回收,即使JVM的内存真的已经不够用了,即使JVM最终不得已抛出了OOM错误,那么该引用继续抢占;
- 软引用(Soft Reference):当我们内存空间不足时,可以回收此内存空间。如果内存空间充足,则不回收。可以用其完成一些缓存的处理操作。
- 弱引用(Weak Reference):不管内存是否紧张,只要一出现GC处理,则立即回收。
- 幽灵引用(Phantom Reference):和没有引用是一样的。
强引用
强引用是java默认支持的一种操作模式,也就是说,在我们引用的处理期间,即使出现有GC,即使内存不足,该引用的数据也不会被回收。
Object oa = new Object(); Object ob = oa; oa = null; //oa对象已经断开了对原始的引用 System.gc(); //进行垃圾回收 System.out.println(ob);
ob对象被打印出来,不是null。
强引用中只可能所有对象断开连接后,才可以成为垃圾空间,才有可能被回收。而即使有一个对象引用此空间,那么该对象也不会被回收。
-Xmx10m -Xms10m -XX:+PrintGCDetails
Object oa = new Object();
Object ob = oa;
oa = null; //oa对象已经断开了对原始的引用
System.gc(); //进行垃圾回收
System.out.println(ob);
while (true){
new Thread(() -> {//lamada表达式
String str = "lihao";
for (int i = 0; i < 1000; i++) {//制造一个错误,产生垃圾。
str += str + i;
str.intern();
}
Object oa = new Object();
Object ob = oa;
oa = null; //oa对象已经断开了对原始的引用
System.gc(); //进行垃圾回收
// System.out.println(ob);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
即使产生了GC,也不会释放强引用的空间。
强引用并不是造成OOM的关键因素,正常来讲,你没一个用户(线程)操作完成后,该对象都可以被很容易的被回收。
软引用
当我们内存不足的时候,才进行GC的空间释放,但是如果想使用软引用,必须使用单独特殊的处理类:java.lang.ref.SoftReference,该类的定义如下:
-
- 构造方法: public SoftReference(T reference)
- 取得引用的数据:public T get()
String str = "lihao";
SoftReference<String> ref = new SoftReference<>(str);
str = null; //强引用断开连接
System.gc(); //因为此时的空间还很富裕,所以软引用不会释放
System.out.println(ref);
与强引用相比,最大的特点在于:软引用中保存的内容如果在内存富裕的时候会继续保留,内存不足时,会作为第一批的丢弃者进行垃圾空间的释放。
在开发中可以利用软引用实现高速缓存组件。
弱引用
弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
WeakReference<String> sr = new WeakReference<String>(new String("lihao"));
System.out.println(sr.get()); //lihao
System.gc(); //通知JVM的gc进行垃圾回收
System.out.println(sr.get()); //null
虚引用(幽灵引用)
虚引用顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的声明周期。如果一个对象仅持有虚引用,那么他就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
ReferenceQueue<String> queue = new ReferenceQueue<String>();
PhantomReference<String> pr = new PhantomReference<String>(new String("lihao"), queue);
System.out.println(pr.get());
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现程序某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
引用队列ReferenceQueue
就是一个引用队列,如果保存的是Reference对象本身,如果:Reference引用指向的对象被GC回收,其实Reference已经无效了。
这种Reference将被放入引用队列,可以在这里将其清除,避免占有空间。
final ReferenceQueue queue = new ReferenceQueue();
String str = new String("lihao");
WeakReference wr = new WeakReference(str, queue);
Thread thread = new Thread() {
@Override
public void run() {
try {
Reference reference = queue.remove();
System.out.println(reference + " event fired.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.setDaemon(true);//标记为守护线程
thread.start();
System.out.println("Reference Queue is listening");
str = null; //断开强引用
System.out.println("wr.get:"+wr.get());
System.out.println("Ready to GC");
System.gc();
try {
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("wr.get:"+wr.get());
WeakHashMap
弱引用map:就是Key键是一个弱引用的键,如果key键被回收,则在get该map中值后,会自动remove掉value。
如果key键始终被强引用,则无法被回收。
注意value是被强引用的,所以不要让value间接的引用了key键,这将导致key始终被强引用。
这个map 适合受key的生命周期控制的缓存。
String test1 = new String("test1");
String test2 = new String("test2");
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put(test1, "test1");
hashMap.put(test2, "test2");
WeakHashMap<String, String> weakMap = new WeakHashMap<>();
weakMap.put(test1, "test1");
weakMap.put(test2, "test2");
weakMap.remove(test1);
//断开强引用
test1 = null;
test2 = null;
System.gc();
for (Map.Entry<String, String> entry : hashMap.entrySet()) {
System.out.println(entry.getKey() + entry.getValue());
}
System.out.println("===============");
for (Map.Entry<String, String> entry : weakMap.entrySet()) {
System.out.println(entry.getKey() + entry.getValue());
}
如何应用软引用避免OOM
假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取,则会严重影响性能,但是如果全部加载到内存当中,又有可能造成内存溢出,此时使用软引用可以解决这个问题。
private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String,SoftReference<Bitmap>>();
public void addBitmapToCache(String path) {
// 强引用的Bitmap对象
Bitmap bitmap = BitmapFactory.decodeFile(path);
// 软引用的Bitmap对象
SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);
// 添加该对象到Map中使其缓存
imageCache.put(path, softBitmap);
}
public Bitmap getBitmapByPath(String path) {
// 从缓存中取软引用的Bitmap对象
SoftReference<Bitmap> softBitmap = imageCache.get(path);
// 判断是否存在软引用
if (softBitmap == null) {
return null;
}
// 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空
Bitmap bitmap = softBitmap.get();
return bitmap;
}