参考原文
http://www.javaspecialists.eu/archive/Issue015.html
SoftReference
Strong Reference
首先解释强引用,是Java默认的引用形式,即一个对象被一个变量直接引用的情况,如:
这种情况下,对象是不会被GC清除的,除非变量的引用解除(例如超出作用域之后,变量被清空)。
WeakReference
WeakReference是对普通引用的封装,表明一个对象被一个变量弱引用。与强引用不同的是,弱引用的对象如果没有其他的强引用指向它,那么GC依然会回收它。
WeakHashMap
设计一个缓存时,需要将缓存的对象保存在一个map中,这里如果普通的HashMap存在问题,可能导致内存OOM。原因在于HashMap保存的是强引用,即使缓存对象在客户端的引用失效了,由于HashMap强引用的存在,这个缓存对象依然不会被GC回收。这就导致如果长时间读写HashMap缓存,会产生OOM。
Sun提供的解决方案是:WeakHashMap。WeakHashMap的key是以WeakReference的形式存在的,一旦这个key对象在程序中没有了其他强引用,那么GC就会在后续考虑回收整个entry,从而保证内存不会一直被这些已经没有用的缓存对象填满。
WeakHashMap的问题在于,实际上作为一个缓存应用,还是希望缓存的对象尽可能多待会儿,也即并不希望一旦没有人使用缓存对象,就立马让GC回收,万一之后马上有人要用了呢?!这里我们就引入了SoftReference来解决这个问题。
SoftReference
比WeakReference更”weak”,即即使没有外部的强引用指向缓存对象,GC依然不回收。只有等到JVM的内存快满了的时候,才回收这些SoftReference对象。这不正是我们希望达到的缓存特性么?(值得一提的是,SUN没有提供这种比WeakHashMap更合理的缓存实现形式,不知为何)
当软可及对象被回收之后,虽然这个SoftReference对象的get()方法返回null,但这个SoftReference对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量SoftReference对象带来的内存泄漏
Soft引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中,关联的方法例如:
那么当这个SoftReference所软引用的aMyOhject被垃圾收集器回收的同时,ref所强引用的SoftReference对象被列入ReferenceQueue。也就是说,ReferenceQueue中保存的对象是Reference对象,而且是已经失去了它所软引用的对象的Reference对象。另外从ReferenceQueue这个名字也可以看出,它是一个队列,当我们调用它的poll()方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个Reference对象。
清除的方法:
摆脱GC控制,用SoftReference实现一个更适合缓存的HashMap
实现的几点优化:
- 每次改变Map(put, remove, clear)或获取map size的时候都去遍历查看哪些SoftReference对象被GC回收了。如何检查?很简单,通过一个ReferenceQueue来实现。
- 自己设计一个LinkedList,保存最近被访问的缓存对象的强引用,避免被GC回收这些最近使用的对象。
- 使用装饰模式来包装原来的HashMap方法
|
|
关于测试程序的几点说明:
byte[] block = new byte[11*_1MB]
希望占用JVM空间,然后触发GC回收SoftReference的缓存对象,但是并不成功。原因在于:
大对象直接在老年代分配,而不会占用Eden空间,也就是Map的存储空间。从这张图可以看出,确实是占用了Old空间,而不是Eden。图中7、8行分别是OC(老年代总空间)和OU(老年代使用空间)。并且由于有强引用的存在,因此虽然Old快满了,GC却无法回收。导致实际测试中,没有任何GC,然后直接爆OOM,虽然Eden和Survivor还有很多空间。(可见无法回收的大对象非常消耗JVM)
正确的方法是,逐步添加1MB的对象。这样保证对象确实会占用年轻代的空间(Eden和Survivor)。如图,产生了minorGC和FullGC,证明Map中的对象被GC清除了。并且,fullGC之后,bytes对象被移到了老年代中,占用了8906KB的空间。还有大约1MB在Eden区中。
我们运行程序的结果也表明,确实SoftHashMap的前面几个对象在JVM内存不足的时候被GC回收了。运行程序的JVM参数:
-Xms15m -Xmx15m
。也可以加上--verbose:gc
在console中打印GC情况。12345678910111213141516Testing class me.util.cache.SoftHashMapOne=1Two=2Three=3Four=4Five=5Three=null, and has been collected by GC.Two=null, and has been collected by GC.One=null, and has been collected by GC.One=nullTwo=nullThree=nullFour=4Five=5如何监控JVM的使用情况
12jps # 找到运行的java程序的vidjstat -gc [vid] 1000 # 每1s显示一次关于jstat输出:
123456789101112131415161718192021222324252627282930313233S0C: Current survivor space 0 capacity (kB).S1C: Current survivor space 1 capacity (kB).S0U: Survivor space 0 utilization (kB).S1U: Survivor space 1 utilization (kB).EC: Current eden space capacity (kB).EU: Eden space utilization (kB).OC: Current old space capacity (kB).OU: Old space utilization (kB).MC: Metaspace capacity (kB).MU: Metacspace utilization (kB).CCSC: Compressed class space capacity (kB).CCSU: Compressed class space used (kB).YGC: Number of young generation garbage collection events.YGCT: Young generation garbage collection time.FGC: Number of full GC events.FGCT: Full garbage collection time.GCT: Total garbage collection time.