相关阅读:java
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(三)ClassLoader实现热加载
JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比
JAVA基础(五)函数式接口-复用,解耦之利刃
JAVA编程思想(一)经过依赖注入增长扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅知足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
HikariPool源码(二)设计思想借鉴
人在职场(一)IT大厂生存法则算法
Java中引用类型有如下几类:数据库
类型 | 描述 |
---|---|
强引用 | 对象具备强引用,不会被垃圾回收,即便发生OutOfMemoryError。 |
软引用 | 对象具备软引用,在内存空间足够时,垃圾回收器不会回收它;当内存空间不足时,就会回收这些对象。 |
弱引用 | 对象具备弱引用,垃圾回收时若是发现了它就会被回收,而无论内存是否足够。 |
虚引用 | 对象具备弱引用,在任什么时候候均可能被垃圾回收器回收。 |
根据软引用和弱引用的特色,他们适合作内存敏感应用的缓存,当内存不足时会被回收,同时须要注意的是这些缓存是否高频访问,若是缓存不可用,会不会致使数据库压力过大而挂掉,若是会则还要考虑熔断,降级等处理。编程
JAVA中,通常对象的定义都是强引用。显式地设置强引用对象为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就能够回收这个对象,具体何时收集这要取决于gc的算法。缓存
这个例子的目的以下:dom
1. 软引用作缓存的通常用法。
2. 验证在垃圾收集后会回收软引用对象。ide
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中获取数据。post
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以前执行:ui
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.
<--感谢三连击,左边点赞和关注。