作者:dmcm0006 | 来源:互联网 | 2023-09-18 00:04
了解内存布局和垃圾回收,对象在堆上创建之后所持有的引用其实是一种变量类型,引用之间可以通过赋值构成一条引用链。从GC Roots 开始 遍历,判断引用是否可达。引用的可达性是判断能够被垃圾回收的基本条件。在某些场景下,即使引用可达,也希望能够根据语义的 强弱引用进行有选择的回收,以保证系统的正常运行。根据引用类型语义的强弱来决定垃圾回收的阶段,我们可以把引用分为强引用、软引用、弱引用和虚引用。后三类引用,本质上可以让开发工程师 通过代码方式来决定对象的垃圾回收时机。
- 强引用,即Strong Reference ,最为常见,如 Object object = new Object();这样的 变量声明和定义就会产生对该对象的强引用。只要对象有强引用执行啊,并且GC Roots 可达,那么java内存回收时,即使濒临内存耗尽也不会回收该对象。
- 软引用,即Soft Reference, 因用力弱于“强引用”,是用在非必须对象的场景。在即 将OOM之前,垃圾回收器会把这些软引用指向的对象加入回收范围,以获得更多的内存空间。主要用来缓存服务器中间结算结果即不需要实时保存的用户行为等。
- 弱引用,即 Weak Reference ,引用强度较前两种更弱,也是用来描述非必须对象的。如果弱引用指定的对象只存在弱引用这一条路,则在下一次YGC时会被回收由于YGC 不确定,弱引用何时被回收也具有不确定性。弱引用主要用于指向某个易消失的对象。在强引用断开后,此引用不会劫持对象。
- 虚引用,即Phantom Reference ,是极弱的一种引用关系,定义完成后吗,就无法通过该引用获取指向的对象。为一个对象设置虚引用的唯一目的就是希望能在这个对象被回收时收到一个系统通知。
对象的引用类型如下图:
举个例子,在房产交易市场中,某个卖家有一套房子,成功出售给某个买家后引用置为null,
这里有4个买家使用4种不同的引用关系指向这套房子。
- 买家是强引用: 如果把seller引用赋值给它,则永久有效,系统不会因为seller= null 就触发对这套房子的回收,这时房屋交易市场最常见的交付方式。
- 买家buyer2是软引用,只要不产生OOM,buyer2.get() 就可以获取房子对象,就像房子是租来的一样。
- 买家buyer3是弱引用,一旦过户后,seller置为null, buyer3的房子持有时间估计只有几秒钟,卖家只是给买家做了一张假的房产证,买家高兴了几秒钟后没发现房子已经不是自己的了。
- buyer4 是虚引用,定义完成后无法访问到房子对象,卖家只是虚构的房源,是空手套白狼的诈骗术。
强引用是最常用的,而虚引用在业务中几乎很难用到,下面我们举例介绍一下软引用和弱引用
先来说一下软引用的回收机制。首先设置JVM参数: -Xms20m -Xmx20m , 即只有20MB的堆空间内容。在下面代码中不断地往集合中添加House对象,而每个House有2000个Door的成员变量,狭小的堆空间加上大对象的产生,就为了尽快触达内存耗尽的临界状态。
强引用
public class SoftReferenceHouse {public static void main(String[] args) {List houses &#61; new ArrayList<>();int i &#61;0;while (true){try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}houses.add(new House());System.out.println("i&#61;&#61;"&#43;(&#43;&#43;i));}}
}class House{private static final Integer DOOR_NUMBER &#61; 2000;public Door[] doors &#61; new Door[DOOR_NUMBER];class Door{}
}
上面代码会一直执行&#xff0c;直到报OOM&#xff0c;因为强引用不会释放。
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat com.yaspeed2.House.(SoftReferenceHouse.java:26)at com.yaspeed2.SoftReferenceHouse.main(SoftReferenceHouse.java:17)
软引用
public class SoftReferenceHouse {public static void main(String[] args) {List> houses &#61; new ArrayList>();int i &#61;0;while (true){SoftReference buyer2 &#61; new SoftReference(new House());houses.add(buyer2);System.out.println("i&#61;&#61;"&#43;(&#43;&#43;i));}}
}class House{private static final Integer DOOR_NUMBER &#61; 4000;public Door[] doors &#61; new Door[DOOR_NUMBER];class Door{}
}
正常运行一段时间&#xff0c;内存到达耗尽的临近状态&#xff0c;House$Door 超过10MB左右&#xff0c;内存占比达到百分之七八十。
软引用的特性在数秒之后产生价值&#xff0c;House对象从千数量级迅速降到百数量级&#xff0c;内存容量迅速被释放出来。保证了程序的正常执行。
软引用SoftReference 的父类 Reference的属性&#xff1a; private T referent, 它指向new House()对象&#xff0c;而SoftReference 的get(),也是调用了super.get() 来访问父类这个私有属性。大量的House 在内存即将耗尽前&#xff0c;成功地一次次被清理掉。
对象buyer2虽然是引用类型&#xff0c;但其本身碍事占用一定内存空间的&#xff0c;它是被集合 ArrayList 强引用劫持的&#xff0c;在不断循环执行 houses.add() 后&#xff0c;终究会产生 OOM。
软引用、弱引用、虚引用均存在带有队列的构造方法
public SoftReference(T referent, ReferenceQueue super T> q) {super(referent, q);this.timestamp &#61; clock;}
可以在队列中检查哪个软引用的对象被回收了&#xff0c;从而把失去House 的软引用对象清理掉。
软引用一般用于在同一服务器内缓存中间结果。如果命中缓存&#xff0c;则提取缓存结果&#xff0c;否则重新计算或获取。但是&#xff0c;软引用肯定不是用来缓存高频数据结构的&#xff0c;万一服务器重启或者软引用触发大规模回收&#xff0c;所有的访问将直接指向数据库&#xff0c;导致数据库压力时大时小&#xff0c;甚至崩溃。
弱引用
代码如下:
public class WeakReferenceWhenIdle {public static void main(String[] args) {House seller &#61; new House();WeakReference buyer3 &#61; new WeakReference<>(seller);seller &#61; null;long start &#61; System.nanoTime();long count &#61; 0;while (true){if(buyer3.get() &#61;&#61; null){long duration &#61; (System.nanoTime()- start)/(1000*1000);System.out.println("house is null and exited time &#61; "&#43;duration &#43;"ms");break;}else{System.out.println("still there.count&#61;"&#43;count&#43;&#43;);}}}
}
执行结果如下:
house is null and exited time &#61; 964ms
HeapPSYoungGen 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 1056768Kclass space used 388K, capacity 390K, committed 512K, reserved 1048576K
这个示例代码在YGC下&#xff0c;可以轻松回收 WeakReference指向的new House() 对象&#xff0c;WeakReference 典型的应用是WeakHashMap中。
在刚才的房源案例中&#xff0c;卖家的房子对应一些列房源资料&#xff0c;如果卖家的房源已经售出&#xff0c;则中介也不需要一直保存相关信息&#xff0c;自动回收存储空间即可&#xff0c;如下代码&#xff1a;
public class WeakHashMapTest {public static void main(String[] args) {House seller1 &#61; new House("1号卖家房源.");SellerInfo sellerInfo1 &#61; new SellerInfo();House seller2 &#61; new House("2号卖家房源");SellerInfo sellerInfo2 &#61; new SellerInfo();WeakHashMap weakHashMap &#61; new WeakHashMap<>();//如果换成 HashMap &#xff0c;则Key是对House对象的强引用weakHashMap.put(seller1,sellerInfo1);weakHashMap.put(seller2,sellerInfo2);System.out.println("weakHashMap before null,size&#61;"&#43;weakHashMap.size());seller1 &#61; null;System.gc();System.runFinalization();//如果换成 HashMap &#xff0c;size 依然等于2System.out.println("weakHashMap after null, size &#61; "&#43;weakHashMap.size());System.out.println(weakHashMap);}
}class SellerInfo{}
执行结果如下:
weakHashMap before null,size&#61;2
weakHashMap after null, size &#61; 1
{House{name&#61;&#39;2号卖家房源&#39;}&#61;com.yaspeed2.SellerInfo&#64;14ae5a5}
如果换成HashMap ,key就是强引用指向House对象&#xff0c;即使seller1&#61;null &#xff0c;也并不影响HashMap这个key被置为null,如果是HashMap ,则最后的size依然等于2&#xff0c;而WeakHashMap就是1&#xff0c;回收seller1的引用。因而WeakHashMap 适用于 缓存不敏感的临时信息的场景。例如&#xff0c;用户登录系统后的浏览路径在关闭浏览器后可以自动清空。