自定义缓存设计方案(LRU)
常用缓存淘汰算法
内存的淘汰机制主要有以下几种:
1、FIFO (First In, First Out)
先进先出算法
2、LFU (Least Frequently Used)
最不经常使用算法
3、LRU (Least Recently Used)
最近最少使用算法
实现思路
链表+HashMap
- 创建时
设定链表的长度
- 查询时直接查询HashMap
- 新数据插入到链表头部;
- 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
- 当链表满的时候,将链表尾部的数据丢弃,同时删除HashMap中对应的数据。
**增强的LinkedHashMap
LinkedHashMap 构造方法
public LinkedHashMap() {
// 调用HashMap的构造方法,其实就是初始化Entry[] table
super();
// 这里是指是否基于访问排序,默认为false
accessOrder = false;
}
- 设置
accessOrder
为true - 新加入的数据和最近访问的数据都会放到链表尾部
- 当链表满的时候,
将链表头部的数据丢弃
Sprint Cloud 组件的自定义实现
Feign
- Https的支持
- 实现本地访问K8s内的服务发现
- 自定义HttpMessageConverter,用于解决消息头部中
Content-Type=text/plain;charset=UTF-8
导致解码失败问题
Zuul
- 与数据库结合实现动态路由和访问过滤
Admin
- 重定向指标输出路径到公司监控平台
其他
- 结合Apollo配置中心实现配置实时更新
- 结合使用场景实现大量默认配置,尽量减少配置数量
- 自定义骨架,降低模块创建成本
堆外内存的管理
堆外内存有什么优势?
减少垃圾回收
:因为垃圾回收会对其他的应用产生影响
加快了复制的速度
:堆内在flush到远程时,会先复制到直接内存(非堆内存),然后再发送;而存储在堆外内存相当于省略掉了这个工作
可以扩展至更大的内存空间
。比如超过1TB甚至比主存还大的空间;
使用堆外内存的缺点
堆外内存的缺点就是内存难以控制
,使用了堆外内存就间接失去了JVM管理内存的可行性,需要自己管理
,当发生内存溢出时排查起来非常困难。
申请堆外内存
# 方法一(推荐)
ByteBuffer.allocateDirect(10 * 1024 * 1024)
# 方法二(不推荐)
unsafe.allocateMemory(size)
堆外内存回收
参考:JVM——堆外内存详解
JDK中使用DirectByteBuffer
对象来表示堆外内存,每个DirectByteBuffer
对象在初始化时,都会创建一个对应的Cleaner
对象,这个Cleaner
对象会在合适的时候执行unsafe.freeMemory(address)
,从而回收这块堆外内存。
当初始化一块堆外内存时,对象的引用关系如下:
其中first
是Cleaner
类的静态变量,Cleaner
对象在初始化时会被添加到Clener
链表中,和first
形成引用关系,ReferenceQueue
是用来保存需要回收的Cleaner
对象。
如果该DirectByteBuffer
对象在一次GC中被回收了,即
此时,只有Cleaner
对象唯一保存了堆外内存的数据(开始地址、大小和容量),在下一次FGC时,把该Cleaner
对象放入到ReferenceQueue
中,并触发clean
方法。
Cleaner
对象的clean
方法主要有两个作用:
- 把自身从
Cleaner
链表删除,从而在下次GC时能够被回收 - 释放堆外内存
在初始化DirectByteBuffer对象时,会自动去判断,如果堆外内存的环境很友好,那么就申请堆外内存;如果当前堆外内存的条件很苛刻时(即有很多无效内存没有得到释放),这时候就会主动调用System.gc()强制执行FGC,从而释放那些无效内存。
为了避免这种悲剧的发生,也可以通过-XX:MaxDirectMemorySize
来指定最大的堆外内存大小,当使用达到了阈值的时候将调用System.gc来做一次full gc,以此来回收掉没有被使用的堆外内存。
两个线程交叉打印1和2
// 打印线程
public class Printer implements Runnable {
private String val;
private AtomicBoolean flag;
private boolean targetFlag;
public Printer(String _val, AtomicBoolean _flag, boolean _targetFlag) {
this.val = _val;
this.flag = _flag;
this.targetFlag = _targetFlag;
}
@Override
public void run() {
while(true) {
boolean currentFlag = this.flag.get();
if(currentFlag == this.targetFlag) {
synchorized(Printer.class) {
if(currentFlag == this.targetFlag) {
System.out.println(this.val);
// flag 置成相反的值
this.flag.set(!currentFlag);
}
}
}
}
}
}
// 主线程
public class Main{
private static AtomicBoolean flag = new AtomicBoolean(false);
public static void main(String[] args) {
Thread printer1 = new Thread(new Printer("1", flag, false));
Thread printer2 = new Thread(new Printer("2", flag, true));
printer1.start();
printer2.start();
Thread.currentThread().join();
}
}