了解内存布局和垃圾回收,对象在堆上创建之后所持有的引用其实是一种变量类型,引用之间可以通过赋值构成一条引用链。从GC Roots 开始 遍历,判断引用是否可达。引用的可达性是判断能够被垃圾回收的基本条件。在某些场景下,即使引用可达,也希望能够根据语义的 强弱引用进行有选择的回收,以保证系统的正常运行。根据引用类型语义的强弱来决定垃圾回收的阶段,我们可以把引用分为强引用、软引用、弱引用和虚引用。后三类引用,本质上可以让开发工程师 通过代码方式来决定对象的垃圾回收时机。

对象的引用类型如下图:

java值类型和引用类型的概念(中的四种引用类型)(1)

举个例子,在房产交易市场中,某个卖家有一套房子,成功出售给某个买家后引用置为null,

这里有4个买家使用4种不同的引用关系指向这套房子。

强引用是最常用的,而虚引用在业务中几乎很难用到,下面我们举例介绍一下软引用和弱引用

先来说一下软引用的回收机制。首先设置JVM参数: -Xms20m -Xmx20m , 即只有20MB的堆空间内容。在下面代码中不断地往集合中添加House对象,而每个House有2000个Door的成员变量,狭小的堆空间加上大对象的产生,就为了尽快触达内存耗尽的临界状态。

强引用

public class SoftReferenceHouse { public static void main(String[] args) { List<House> houses = new ArrayList<>(); int i =0; while (true){ try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } houses.add(new House()); System.out.println("i==" ( i)); } } } class House{ private static final Integer DOOR_NUMBER = 2000; public Door[] doors = new Door[DOOR_NUMBER]; class Door{} }

java值类型和引用类型的概念(中的四种引用类型)(2)

上面代码会一直执行,直到报OOM,因为强引用不会释放。

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at com.yaspeed2.House.<init>(SoftReferenceHouse.java:26) at com.yaspeed2.SoftReferenceHouse.main(SoftReferenceHouse.java:17)

软引用

public class SoftReferenceHouse { public static void main(String[] args) { List<SoftReference<House>> houses = new ArrayList<SoftReference<House>>(); int i =0; while (true){ SoftReference<House> buyer2 = new SoftReference<House>(new House()); houses.add(buyer2); System.out.println("i==" ( i)); } } } class House{ private static final Integer DOOR_NUMBER = 4000; public Door[] doors = new Door[DOOR_NUMBER]; class Door{} }

正常运行一段时间,内存到达耗尽的临近状态,House$Door 超过10MB左右,内存占比达到百分之七八十。

java值类型和引用类型的概念(中的四种引用类型)(3)

软引用的特性在数秒之后产生价值,House对象从千数量级迅速降到百数量级,内存容量迅速被释放出来。保证了程序的正常执行。

软引用SoftReference 的父类 Reference的属性: private T referent, 它指向new House()对象,而SoftReference 的get(),也是调用了super.get() 来访问父类这个私有属性。大量的House 在内存即将耗尽前,成功地一次次被清理掉。

对象buyer2虽然是引用类型,但其本身碍事占用一定内存空间的,它是被集合 ArrayList 强引用劫持的,在不断循环执行 houses.add() 后,终究会产生 OOM。

软引用、弱引用、虚引用均存在带有队列的构造方法

public SoftReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); this.timestamp = clock; }

可以在队列中检查哪个软引用的对象被回收了,从而把失去House 的软引用对象清理掉。

软引用一般用于在同一服务器内缓存中间结果。如果命中缓存,则提取缓存结果,否则重新计算或获取。但是,软引用肯定不是用来缓存高频数据结构的,万一服务器重启或者软引用触发大规模回收,所有的访问将直接指向数据库,导致数据库压力时大时小,甚至崩溃。

弱引用代码如下:

public class WeakReferenceWhenIdle { public static void main(String[] args) { House seller = new House(); WeakReference<House> buyer3 = new WeakReference<>(seller); seller = null; long start = System.nanoTime(); long count = 0; while (true){ if(buyer3.get() == null) { long duration = (System.nanoTime()- start)/(1000*1000); System.out.println("house is null and exited time = " duration "ms"); break; }else{ System.out.println("still there.count=" count ); } } } }

执行结果如下:

house is null and exited time = 964ms Heap PSYoungGen total 75776K, used 2733K [0x000000076bf80000, 0x0000000771400000, 0x00000007c0000000) eden space 65024K, 2% used [0x000000076bf80000,0x000000076c161470,0x000000076ff00000) from space 10752K, 7% used [0x000000076ff00000,0x000000076ffca020,0x0000000770980000) to space 10752K, 0% used [0x0000000770980000,0x0000000770980000,0x0000000771400000) ParOldGen total 173568K, used 8K [0x00000006c3e00000, 0x00000006ce780000, 0x000000076bf80000) object space 173568K, 0% used [0x00000006c3e00000,0x00000006c3e02000,0x00000006ce780000) Metaspace used 3541K, capacity 4502K, committed 4864K, reserved 1056768K class space used 388K, capacity 390K, committed 512K, reserved 1048576K

这个示例代码在YGC下,可以轻松回收 WeakReference指向的new House() 对象,WeakReference 典型的应用是WeakHashMap中。

在刚才的房源案例中,卖家的房子对应一些列房源资料,如果卖家的房源已经售出,则中介也不需要一直保存相关信息,自动回收存储空间即可,如下代码:

public class WeakHashMapTest { public static void main(String[] args) { House seller1 = new House("1号卖家房源."); SellerInfo sellerInfo1 = new SellerInfo(); House seller2 = new House("2号卖家房源"); SellerInfo sellerInfo2 = new SellerInfo(); WeakHashMap<House,SellerInfo> weakHashMap = new WeakHashMap<>(); //如果换成 HashMap ,则Key是对House对象的强引用 weakHashMap.put(seller1,sellerInfo1); weakHashMap.put(seller2,sellerInfo2); System.out.println("weakHashMap before null,size=" weakHashMap.size()); seller1 = null; System.gc(); System.runFinalization(); //如果换成 HashMap ,size 依然等于2 System.out.println("weakHashMap after null, size = " weakHashMap.size()); System.out.println(weakHashMap); } } class SellerInfo{}

执行结果如下:

weakHashMap before null,size=2 weakHashMap after null, size = 1 {House{name='2号卖家房源'}=com.yaspeed2.SellerInfo@14ae5a5}

如果换成HashMap ,key就是强引用指向House对象,即使seller1=null ,也并不影响HashMap这个key被置为null,如果是HashMap ,则最后的size依然等于2,而WeakHashMap就是1,回收seller1的引用。因而WeakHashMap 适用于 缓存不敏感的临时信息的场景。例如,用户登录系统后的浏览路径在关闭浏览器后可以自动清空。

,