keycloak~深入了解DefaultSegmentedDataContainer对象解决内存泄漏
- 源码来自infinispan: git@github.com:infinispan/infinispan.git
基于Infinispan 内部架构的了解,让我为你详细解释 DefaultSegmentedDataContainer 中 TTL 的存储位置:
DefaultSegmentedDataContainer 中 TTL 的存储结构
核心结构层次
DefaultSegmentedDataContainer
│
├── DataSegment[] segments ← 分段数组(根据 key 的 hash 分配)
│ │
│ └── ConcurrentHashMap<Object, InternalCacheEntry>
│ │
│ └── InternalCacheEntry<K, V> ← 每个缓存条目对象
│ │
│ ├── key : K ← 缓存键
│ ├── value : V ← 缓存值
│ ├── metadata : Metadata ← 元数据(包含TTL信息)★
│ ├── created : long ← 创建时间戳
│ └── lastUsed : long ← 最后访问时间戳
TTL 信息的具体位置
TTL 不是存储在 DefaultSegmentedDataContainer 的直接属性中,而是在每个 InternalCacheEntry 对象的 Metadata 属性里。
Infinispan 中有多种 InternalCacheEntry 实现类,根据是否有过期时间分类:
| 实现类 | 特点 | TTL 相关属性 |
|---|---|---|
ImmortalCacheEntry |
永不过期 | 无 TTL 属性 |
MortalCacheEntry |
有 lifespan | lifespan, created |
TransientCacheEntry |
有 maxIdle | maxIdle, lastUsed |
TransientMortalCacheEntry |
同时有两者 | lifespan, maxIdle, created, lastUsed |
MetadataImmortalCacheEntry |
使用 Metadata 对象 | metadata.lifespan(), metadata.maxIdle() |
MetadataMortalCacheEntry |
使用 Metadata 对象 | metadata.lifespan(), metadata.maxIdle() |
下面的缓存sessions表示永不过期,因为它的类型是ImmortalCacheEntry

下面的缓存sessions是有过期时间的,它的类型是TransientMortalCacheEntry

实际的 TTL 属性
对于 Keycloak 的会话缓存,通常使用 **带 Metadata 的条目**,TTL 信息在:
InternalCacheEntry entry = ...;
// 获取 lifespan (生命周期 TTL)
long lifespan = entry.getLifespan(); // 返回毫秒值,-1 表示永不过期
// 获取 maxIdle (空闲 TTL)
long maxIdle = entry.getMaxIdle(); // 返回毫秒值,-1 表示永不过期
// 获取创建时间
long created = entry.getCreated(); // 创建时的时间戳
// 获取最后访问时间
long lastUsed = entry.getLastUsed(); // 最后访问的时间戳
// 检查是否过期
boolean expired = entry.isExpired(System.currentTimeMillis());
// 计算过期时间点
long expiryTime = entry.expiryTime(); // 返回具体的过期时间戳
在 JVM 调试时查看 TTL
如果你想在调试器中查看某个缓存项的 TTL,路径是:
DefaultSegmentedDataContainer
└── segments (DataSegment[])
└── [segment index]
└── entries (ConcurrentHashMap)
└── [key]
└── InternalCacheEntry 对象
├── metadata
│ ├── lifespan ← 这里是 TTL(毫秒)
│ └── maxIdle ← 这里是空闲超时(毫秒)
├── created ← 创建时间戳
└── lastUsed ← 最后访问时间戳
过期判断逻辑
// 在 InternalCacheEntry 中的过期检查逻辑(简化版):
public boolean isExpired(long now) {
// lifespan 检查
if (lifespan > 0 && (created + lifespan) <= now) {
return true;
}
// maxIdle 检查
if (maxIdle > 0 && (lastUsed + maxIdle) <= now) {
return true;
}
return false;
}
Keycloak 中 TTL 的设置来源
当 Keycloak 写入会话时,TTL 值来自 SessionTimeouts 类的计算:
// 写入缓存时设置 TTL
cache.put(key, value, lifespanMs, TimeUnit.MILLISECONDS, maxIdleMs, TimeUnit.MILLISECONDS);
这些值最终会被封装到 InternalCacheEntry 的 metadata 或对应属性中。
DefaultSegmentedDataContainer
│
├── DataSegment[] segments ← 分段数组(根据 key 的 hash 分配)
│ │
│ └── ConcurrentHashMap<Object, InternalCacheEntry>
│ │
│ └── InternalCacheEntry<K, V> ← 每个缓存条目对象
│ │
│ ├── key : K ← 缓存键
│ ├── value : V ← 缓存值
│ ├── metadata : Metadata ← 元数据(包含TTL信息)★
│ ├── created : long ← 创建时间戳
│ └── lastUsed : long ← 最后访问时间戳
InternalCacheEntry entry = ...;
// 获取 lifespan (生命周期 TTL)
long lifespan = entry.getLifespan(); // 返回毫秒值,-1 表示永不过期
// 获取 maxIdle (空闲 TTL)
long maxIdle = entry.getMaxIdle(); // 返回毫秒值,-1 表示永不过期
// 获取创建时间
long created = entry.getCreated(); // 创建时的时间戳
// 获取最后访问时间
long lastUsed = entry.getLastUsed(); // 最后访问的时间戳
// 检查是否过期
boolean expired = entry.isExpired(System.currentTimeMillis());
// 计算过期时间点
long expiryTime = entry.expiryTime(); // 返回具体的过期时间戳
DefaultSegmentedDataContainer
└── segments (DataSegment[])
└── [segment index]
└── entries (ConcurrentHashMap)
└── [key]
└── InternalCacheEntry 对象
├── metadata
│ ├── lifespan ← 这里是 TTL(毫秒)
│ └── maxIdle ← 这里是空闲超时(毫秒)
├── created ← 创建时间戳
└── lastUsed ← 最后访问时间戳
// 在 InternalCacheEntry 中的过期检查逻辑(简化版):
public boolean isExpired(long now) {
// lifespan 检查
if (lifespan > 0 && (created + lifespan) <= now) {
return true;
}
// maxIdle 检查
if (maxIdle > 0 && (lastUsed + maxIdle) <= now) {
return true;
}
return false;
}
// 写入缓存时设置 TTL
cache.put(key, value, lifespanMs, TimeUnit.MILLISECONDS, maxIdleMs, TimeUnit.MILLISECONDS);
浙公网安备 33010602011771号