一个系统里有成千上万个类,如果不及时清理的话,就会造成内存溢出。
java在jdk1.2版本的时候优化了提供了类来和GC进行交互,回收长时不用的对象,释放内存空间。避免内存溢出。
这些类都在
java.lang.ref
包下。
而一个类的清理则分为四个等级,依次是强,软,弱,虚四个等级。
强引用
下面这行代码就是强引用。
Object obj = new Object();
下面这行代码,gc会回收obj对象。
Object obj = new Object();
obj = null;
只要有任何的引用指向了这个对象,jvm无论如何都不会对该对象进行回收。即使抛出了OOM异常。反之没有任何引用指向该对象,那么对象就会被回收释放内存空间,何时回收就要看gc的内部算法。
但是我们可以通过System.gc()来让gc回收的快点。
软引用
电脑里c盘满了,这时候我们会清理磁盘,删除对我们来说不是非常重要的东西。
这就是软引用的一个体现,被删除的那些东西我们会放在磁盘不去动他,但是当磁盘空间真的吃紧的时候我们就要清理。
但是其实不需要的东西还挺多的,有那么十几个G,那么到底应该删什么呢,这时候就会遵循有用原则,这些东西按照最近一次使用的时间来进行排序,从最长时间不用的东西开始删一直到最近使用。
java提供了
SoftReference<T>
该类会缓存对象,当jvm虚拟机内存不足时,会回收软引用引用的对象。并且同时或者稍后时间会把该软引用对象放到与之关联的引用队列中。
StringBuilder softSB = new StringBuilder("softReference stringBuilder..."); SoftReference<StringBuilder> softReference = new SoftReference<StringBuilder>(softSB); System.out.println("设null之前---缓存 "+softReference.get());//SoftReference stringBuilder... softSB = null; System.out.println("-------------------我是分割线----------------"); System.gc(); System.out.println("设null之后---缓存 "+softReference.get());//SoftReference stringBuilder...
最开始两句的代码意思就是把StringBuilder对象缓存到softReference,可以看到softReference.get()输出了softReference stringBuilder...
下面我把softSB指向了null,并且通知gc快速回收垃圾对象,但是依旧可以通过softReference.get()获取到对象,因为我们的内存内存不吃紧,因此gc并不会回收他。
这时候我们把软引用对象和一个引用队列关联起来。
ReferenceQueue<StringBuilder> softReferenceQueue = new ReferenceQueue<>(); StringBuilder softSB = new StringBuilder("softReference stringBuilder..."); SoftReference<StringBuilder> softReference = new SoftReference<StringBuilder>(softSB); System.out.println("设null之前---缓存 "+softReference.get());//SoftReference stringBuilder... System.out.println("设null之前---队列 "+softReferenceQueue.poll());//null softSB = null; System.out.println("-------------------我是分割线----------------"); System.gc(); System.out.println("设null之后---缓存 "+softReference.get());//SoftReference stringBuilder... System.out.println("设null之后---队列 "+softReferenceQueue.poll());//null
因为内存足够,软引用引用的对象并没有被收回,所以软引用对象也没有被放到队列中去。为了验证gc真的会在内存吃紧的时候回收软引用引用的对象,我们来看如下代码。
ReferenceQueue referenceQueue = new ReferenceQueue(); ArrayList<Object> objList = new ArrayList<>(25565282); for (int i = 0; i < 25565282; i++) { objList.add(new Object()); } SoftReference<ArrayList<Object>> softReference = new SoftReference<>(objList, referenceQueue); objList = null; System.out.println("1.."+ (softReference.get()!= null));//1..true System.out.println("2.."+referenceQueue.poll());//2..null List<Object> list = new ArrayList<>(); SoftReference sf = null; while((sf=(SoftReference)referenceQueue.poll()) == null){ list.add(new Object()); } System.out.println("3.."+(softReference.get()!= null));//3..false System.out.println("4.."+(sf == softReference));//4..true System.out.println("5.."+sf.get());//5..null
以上代码来自站内 @ zs808 的手笔,在此再次感谢!帮我解惑了这一个问题。
当list集合添加足够多的对象时,内存吃紧,因此gc回收了了softReference软引用引用的objList集合,释放了空间,并且把softReference放到了与之关联的referenceQueue队列当中。referenceQueue.poll()取出有值赋值给ref。判断为false,跳出while。并且这时候再去从softReference里面获取objList已经拿不到东西了。队列取出的sf就是softReference。
弱引用
WeakReference
弱引用,比起软引用的如内存足够则放着,不足则清理的来说。jvm对于这类对象则是看到了就要清理,不管内存是否足够与否。
这里和我们平时
Objectt obj = new Object();
obj = null;
不知底层到底有什么不同,但是效果类似。
同样的,弱引用可以和一个引用队列相关联。当回收了弱引用缓存的对象,则同时或者稍后会将弱引用对象放到与之关联的引用队列当中,注意这个动作不是立刻完成的。
ReferenceQueue<StringBuilder> weakReferenceQueue = new ReferenceQueue<>(); StringBuilder weakSB = new StringBuilder("weakReference stringBuilder..."); WeakReference<StringBuilder> weakReference = new WeakReference<StringBuilder>(weakSB,weakReferenceQueue); System.out.println(weakReference.hashCode()); System.out.println("设null之前---缓存 "+weakReference.get());//weakReference stringbuilder... System.out.println("设null之前---队列 "+weakReferenceQueue.poll());//null weakSB = null; System.out.println("-------------------我是分割线----------------"); System.gc(); // Thread.currentThread().sleep(1000); WeakReference weakReference1 = (WeakReference)weakReferenceQueue.poll(); System.out.println(weakReference1);//null System.out.println("设null之后---缓存 "+weakReference.get());//null System.out.println("设null之前---队列 "+(weakReference1 == weakReference));//null
以上代码中,如果当前线程没有休眠一秒钟,队列中就取不到弱引用对象,所以最后第三行输出null,打开线程休眠那一行代码,队列才能取到弱引用对象,并且和上面的弱引用比较是相同对象。
这里我也有疑问:为何只有弱引用需要让线程暂停一段时间,而虚引用则不需要。
虚引用
PhantomReference
虚引用对象只是标志引用的对象何时被gc回收,本身虚引用对象不会缓存对象。你也从它这里拿不到任何对象。
因此,虚引用最大的用处就是和引用队列的集合,事实上,虚引用也必须和引用队列一起使用才可以。虚引用的构造方法中必须传队列参数。
和弱引用不同的是,gc回收了虚引用引用的对象后,它会同时或者晚些时间把虚引用对象放入到与之相关联的引用队列中。可以说虚引用和队列是最好的结合。
ReferenceQueue<StringBuilder> phantomReferenceQueue = new ReferenceQueue<>(); StringBuilder phantomSB = new StringBuilder("phantomReference stringBuilder"); PhantomReference<StringBuilder> phantomReference = new PhantomReference<>(phantomSB,phantomReferenceQueue); System.out.println("设null之前---缓存 "+phantomReference.get());//null System.out.println("设null之前---队列 "+phantomReferenceQueue.poll());//null phantomSB = null; System.out.println("-------------------我是分割线----------------"); System.gc(); System.out.println("设null之后---缓存 "+phantomReference.get());//null System.out.println("设null之前---队列 "+((PhantomReference)phantomReferenceQueue.poll() == phantomReference));//true就像上面说的,你向虚引用get引用的对象也一直都是null。
从引用强度上来说
强引用 > 软引用 > 弱引用 > 虚引用