JDK三级缓存的基本概念
JDK三级缓存通常指处理器(CPU)中的多级缓存机制,包括L1、L2和L3缓存。L1和L2缓存为每个核心私有,L3缓存为所有核心共享。这种设计平衡了访问速度与存储容量,L1最快但容量最小,L3最慢但容量最大。
缓存层级结构
L1缓存
分为指令缓存(L1i)和数据缓存(L1d),通常为32-64KB,延迟约1-4个时钟周期。每个核心独占,访问速度最快。
L2缓存
容量通常在256KB-1MB之间,延迟约10-20个时钟周期。部分设计中为每个核心私有,部分为多个核心共享。
L3缓存
容量从几MB到几十MB不等,延迟约40-100个时钟周期。所有核心共享,用于减少访问主存的频率,提升多核协作效率。
缓存一致性协议
多核环境下,JDK依赖MESI(Modified, Exclusive, Shared, Invalid)协议维护缓存一致性。每个缓存行通过状态标记确保数据一致性:
- Modified: 缓存行被修改,与主存不一致。
- Exclusive: 缓存行与主存一致,且仅当前核心持有。
- Shared: 缓存行与主存一致,且可能被多个核心共享。
- Invalid: 缓存行数据无效。
缓存替换策略
常见策略包括LRU(最近最少使用)和伪LRU。当缓存满时,系统根据策略淘汰旧数据:
- LRU: 优先淘汰最久未访问的缓存行。
- 伪LRU: 近似实现LRU,降低硬件复杂度。
缓存预取机制
硬件或软件预取数据到缓存,减少访问延迟:
- 硬件预取: 处理器根据访问模式预测并加载数据。
- 软件预取: 通过特定指令(如
prefetch
)提示处理器提前加载数据。
代码层面的优化
Java应用中可通过以下方式优化缓存利用率:
// 1. 数据局部性:顺序访问数组
for (int i = 0; i < array.length; i++) {
sum += array[i];
}
// 2. 避免伪共享:使用填充或@Contended注解
@jdk.internal.vm.annotation.Contended
class Counter {
private volatile long value;
}
性能监控工具
使用工具如perf
或JDK的hsdis
分析缓存命中率:
perf stat -e cache-references,cache-misses java YourProgram
通过理解三级缓存机制和优化代码,可以显著提升Java程序的性能。
Spring三级缓存机制概述
Spring框架解决循环依赖问题时采用三级缓存机制,主要涉及singletonObjects
、earlySingletonObjects
和singletonFactories
三个缓存层级。其核心目的是在Bean创建过程中处理相互依赖的情况。
三级缓存的具体作用
singletonObjects(一级缓存)
存储完全初始化完成的单例Bean。这是最终的Bean存储位置,一旦Bean被完全创建并解决所有依赖后,会存放在这里。
earlySingletonObjects(二级缓存)
存储提前曝光的单例Bean(未完全初始化)。当Bean还在创建过程中但已实例化后,会临时存放在这里,用于解决循环依赖。
singletonFactories(三级缓存)
存储Bean的工厂对象(ObjectFactory
)。在Bean实例化后、属性填充前,会将生成Bean的工厂对象放入此缓存,必要时通过工厂提前生成代理对象。
解决循环依赖的流程
-
从一级缓存获取Bean
创建Bean时首先检查singletonObjects
,若存在直接返回已完成的Bean。 -
标记Bean为创建中
若一级缓存未命中,将Bean标记为“创建中”状态,防止重复创建。 -
实例化Bean并放入三级缓存
通过反射调用构造器实例化对象,随后将Bean的工厂对象(用于生成早期引用)存入singletonFactories
。 -
属性填充与依赖处理
填充Bean属性时若发现依赖其他Bean,触发依赖Bean的创建流程。若依赖的Bean也在创建中,会从三级缓存获取早期引用。 -
升级到二级缓存
当通过工厂获取早期引用后,该引用会被转移到earlySingletonObjects
,同时从三级缓存移除工厂对象。 -
初始化完成
所有依赖解决后,Bean完成初始化,存入一级缓存,并清除二级缓存中的临时对象。
代码示例说明
以下是简化版的缓存操作逻辑(非Spring源码):
// 从缓存获取Bean
Object getSingleton(String beanName) {
Object bean = singletonObjects.get(beanName);
if (bean == null && isSingletonCurrentlyInCreation(beanName)) {
bean = earlySingletonObjects.get(beanName);
if (bean == null) {
ObjectFactory<?> factory = singletonFactories.get(beanName);
if (factory != null) {
bean = factory.getObject();
earlySingletonObjects.put(beanName, bean);
singletonFactories.remove(beanName);
}
}
}
return bean;
}
关键设计点
工厂对象的作用
三级缓存使用ObjectFactory
而非直接存储对象,是为了处理AOP代理等场景。工厂可以在必要时生成代理对象,确保依赖注入的一致性。
二级缓存的意义
避免重复调用工厂的getObject()
方法,提升性能。同时作为一级缓存与三级缓存的中间层,降低并发情况下的复杂度。
循环依赖的限制
构造器注入无法通过三级缓存解决,因为实例化前无法提前暴露引用。仅适用于Setter注入或字段注入的场景。