Java中引用类型有以下几类:
类型 | 描述 |
---|---|
强引用 | 对象具有强引用,不会被垃圾回收,即使发生OutOfMemoryError。 |
软引用 | 对象具有软引用,在内存空间足够时,垃圾回收器不会回收它;当内存空间不足时,就会回收这些对象。 |
弱引用 | 对象具有弱引用,垃圾回收时如果发现了它就会被回收,而不管内存是否足够。 |
虚引用 | 对象具有弱引用,在任何时候都可能被垃圾回收器回收。 |
根据软引用和弱引用的特点,他们适合做内存敏感应用的缓存,当内存不足时会被回收,同时需要注意的是这些缓存是否高频访问,如果缓存不可用,会不会导致数据库压力过大而挂掉,如果会则还要考虑熔断,降级等处理。
JAVA中,一般对象的定义都是强引用。显式地设置强引用对象为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象,具体什么时候收集这要取决于gc的算法。
这个例子的目的如下:
1. 软引用做缓存的一般用法。 2. 验证在垃圾收集后会回收软引用对象。import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; public class SoftReferenceDemo { public static void main(String[] args) { OrderManager orderManager = new OrderManager(); // 获取订单线程,不断获取订单,验证系统内存不足后缓存数据会被清理掉,重新从数据库获取。 new Thread(()->{ while (true) { orderManager.getOrder("101"); quietlySleep(2000); } }).start(); // 不断创建新对象,模拟内存不足导致垃圾回收,新对象也是软引用,这样可以被回收,避免OOM异常。 new Thread(()->{ List<SoftReference<BigObject>> list = new ArrayList<>(); while (true) { list.add(new SoftReference<>(new BigObject())); quietlySleep(50); } }).start(); // 主线程休眠等待 quietlySleep(20 * 60 * 1000); } private static void quietlySleep(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } // 模拟大对象 private static class BigObject { byte[] b = new byte[4 * 1024]; } } class OrderManager { public Order getOrder(String id) { Order order = OrderCache.getInstance().getCachedOrder(id); if (order == null) { order = getOrderFromDB(id); } return order; } private Order getOrderFromDB(String id) { Order order = new Order(id, (int) (Math.random() * 100)); System.out.println(new Date() + " get order from DB. XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX " + order); OrderCache.getInstance().cache(order); return order; } } class OrderCache { private static volatile OrderCache singlonCache; private HashMap<String, OrderReference> refChache; private ReferenceQueue queue; private OrderCache(){ this.queue=new ReferenceQueue(); this.refChache=new HashMap<>(); } public static OrderCache getInstance(){ if(singlonCache == null){ synchronized (OrderCache.class) { if (singlonCache == null) { singlonCache=new OrderCache(); } } } return singlonCache; } public void cache(Order order){ cleanCache();//清除已经标记为垃圾的引用 OrderReference reference = new OrderReference(order, queue); refChache.put(order.getId(), reference);//将对象的软引用保存到缓存中 } public Order getCachedOrder(String key){ Order order = null; if (refChache.containsKey(key)){ order= (Order) refChache.get(key).get(); System.out.println(new Date() + " get order from cache. " + order); } return order; } private void cleanCache(){ OrderReference reference = null; while ((reference = (OrderReference)queue.poll()) != null){ System.out.println(new Date() + " cleanCache"); refChache.remove(reference._key); } } static class OrderReference extends SoftReference { private String _key; public OrderReference(Order referent, ReferenceQueue q) { super(referent, q); _key = referent.getId(); } } } class Order { private String id; private long price; public Order(String id, long price) { this.id = id; this.price = price; } public String getId() { return id; } @Override public String toString() { return "order id: " + id + ", price: " + price; } } 复制代码
调整启动参数内存大小,使得更容易满足内存不足场景。
Sun Apr 05 17:46:18 GMT+08:00 2020 get order from DB. XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX order id: 101, price: 21 Sun Apr 05 17:46:20 GMT+08:00 2020 get order from cache. order id: 101, price: 21 Sun Apr 05 17:46:22 GMT+08:00 2020 get order from cache. order id: 101, price: 21 Sun Apr 05 17:46:24 GMT+08:00 2020 get order from cache. order id: 101, price: 21 Sun Apr 05 17:46:26 GMT+08:00 2020 get order from cache. order id: 101, price: 21 Sun Apr 05 17:46:28 GMT+08:00 2020 get order from cache. order id: 101, price: 21 Sun Apr 05 17:46:30 GMT+08:00 2020 get order from cache. order id: 101, price: 21 Sun Apr 05 17:46:32 GMT+08:00 2020 get order from cache. order id: 101, price: 21 Sun Apr 05 17:46:34 GMT+08:00 2020 get order from cache. order id: 101, price: 21 Sun Apr 05 17:46:36 GMT+08:00 2020 get order from cache. order id: 101, price: 21 Sun Apr 05 17:46:38 GMT+08:00 2020 get order from cache. order id: 101, price: 21 Sun Apr 05 17:46:40 GMT+08:00 2020 get order from cache. order id: 101, price: 21 Sun Apr 05 17:46:42 GMT+08:00 2020 get order from cache. order id: 101, price: 21 Sun Apr 05 17:46:44 GMT+08:00 2020 get order from cache. order id: 101, price: 21 Sun Apr 05 17:46:46 GMT+08:00 2020 get order from cache. order id: 101, price: 21 Sun Apr 05 17:46:48 GMT+08:00 2020 get order from cache. order id: 101, price: 21 Sun Apr 05 17:46:50 GMT+08:00 2020 get order from cache. null Sun Apr 05 17:46:50 GMT+08:00 2020 get order from DB. XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX order id: 101, price: 87 Sun Apr 05 17:46:50 GMT+08:00 2020 cleanCache Sun Apr 05 17:46:52 GMT+08:00 2020 get order from cache. order id: 101, price: 87 Sun Apr 05 17:46:54 GMT+08:00 2020 get order from cache. order id: 101, price: 87 复制代码
可以看到当JVM内存不足,做垃圾回收后软引用会被回收,此时从缓存中无法获得数据,会重新从DB中获取数据。
import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; public class WeakReferenceDemo { public static void main(String args[]) { final int size = 3; List<WeakReference<DBConnection>> weakList = new ArrayList<WeakReference<DBConnection>>(); for (int i = 0; i < size; i++) { DBConnection dbConnection = new DBConnection("DBConnection-" + i); weakList.add(new WeakReference(dbConnection)); System.out.println(dbConnection + " be created."); } checkDBConnection(weakList); quietlySleep(20000); // 休眠时间调整到你有足够时间在gc之前输入命令 jmap -histo:live <pid> >beforegc.txt,并能在gc之前完成信息收集 System.gc(); // 不要通过在这里打断点来执行jmap命令,当暂停到断点时,jmap命令也会暂停执行,断点恢复后,会分不清jsmp收集的是GC前还是GC后的信息 System.out.println("gc be called."); quietlySleep(1000); // 这里占用内存少,很快就回收了,占用内存大的就多给点时间 checkDBConnection(weakList); // 这里可以打个断点,以让你知道可以输入命令 jmap -histo:live <pid> >aftergc.txt quietlySleep(1000); // 休眠时间调整到在程序退出前有足够时间完成信息收集 } // 检查DBConnection是否被垃圾回收 private static void checkDBConnection(List<WeakReference<DBConnection>> weakList) { for (WeakReference<DBConnection> ref: weakList) { DBConnection dbConnection = ref.get(); System.out.println("dbConnection is null ? " + (dbConnection == null)); } } // 让我安静睡会 private static void quietlySleep(long timeMillis) { try { Thread.sleep(timeMillis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } // 模拟数据库连接资源 class DBConnection { public String id; public DBConnection(String id) { this.id = id; } public String getId() { return id; } @Override public String toString() { return id; } } 复制代码
DBConnection-0 be created. DBConnection-1 be created. DBConnection-2 be created. dbConnection is null ? false dbConnection is null ? false dbConnection is null ? false gc be called. dbConnection is null ? true dbConnection is null ? true dbConnection is null ? true 复制代码
按照代码中的说明通过jmap命令验证对象是否被回收,在gc之前执行:
jmap -histo 21636 >a.txt 复制代码
注意修改进程号,可通过以下命令查看进程号:
jps |findstr WeakReferenceDemo 复制代码
在GC之后执行:
jmap -histo 21636 >b.txt 复制代码
打开a.txt和b.txt文件查找DBConnection对象,只能在a.txt中找到,而b.txt中找步到,说明确实被回收了。
在HikariPool中也使用弱引用做缓存,参考 HikariPool源码(二)设计思想借鉴 。
没有找到合适的例子和用法。
end.
<--感谢三连击,左边点赞和关注。