本地缓存使用
前言
Github:https://github.com/HealerJean
一、JVM
缓存
1、简单本地缓存
@Slf4j
@Service
public class BypassTaskCacheImpl implements BypassTaskCacheService {
/**
* key 为版本号,value为旁路配置
*/
private Map<String, BypassTaskConfigDto> bypassTaskMap;
/**
* 实时旁路 realtimeBypass
*/
private List<BypassTaskConfigDto> realtimeBypassList;
/**
* 历史旁路 historyBypass
*/
private List<BypassTaskConfigDto> historyBypassList;
/**
* bypassTaskMapper
*/
@Resource
private BypassTaskMapper bypassTaskMapper;
/**
* umpAlarmService
*/
@Resource
private UmpAlarmService umpAlarmService;
/**
* 启动就加载
*/
@PostConstruct
public void init() {
try {
log.info("[BypassTaskSwitchCache#init]:start");
BypassTaskQuery taskQuery = new BypassTaskQuery();
taskQuery.setType(BypassEnum.BypassTypeEnum.REALTIME.getCode());
taskQuery.setTaskStatus(BypassEnum.BypassTaskStatusEnum.EXECUTING.getCode());
taskQuery.setStatus(StatusEnum.VALID.getCode());
Map<String, BypassTaskConfigDto> newMap = new HashMap<>(8);
List<BypassTask> dbRealtimeBypassTasks = bypassTaskMapper.selectByExample(taskQuery);
List<BypassTaskConfigDto> newRealtimeBypassList = new ArrayList<>();
if (!CollectionUtils.isEmpty(dbRealtimeBypassTasks)) {
for (BypassTask bypassTask : dbRealtimeBypassTasks) {
BypassTaskConfigDto bypassTaskConfigDto = BeanUtils.bypassTaskToConfigDto(bypassTask);
newRealtimeBypassList.add(bypassTaskConfigDto);
newMap.put(bypassTask.getVersion(), bypassTaskConfigDto);
}
}
taskQuery = new BypassTaskQuery();
taskQuery.setType(BypassEnum.BypassTypeEnum.HISTORY.getCode());
taskQuery.setTaskStatusList(Arrays.asList(BypassEnum.BypassTaskStatusEnum.CREATED.getCode(), BypassEnum.BypassTaskStatusEnum.EXECUTING.getCode()));
taskQuery.setStatus(StatusEnum.VALID.getCode());
List<BypassTask> dbHistoryBypassTasks = bypassTaskMapper.selectByExample(taskQuery);
List<BypassTaskConfigDto> newHistoryBypassList = new ArrayList<>();
if (!CollectionUtils.isEmpty(dbHistoryBypassTasks)) {
for (BypassTask bypassTask : dbHistoryBypassTasks) {
BypassTaskConfigDto bypassTaskConfigDto = BeanUtils.bypassTaskToConfigDto(bypassTask);
newHistoryBypassList.add(bypassTaskConfigDto);
newMap.put(bypassTask.getVersion(), bypassTaskConfigDto);
}
}
bypassTaskMap = newMap;
realtimeBypassList = newRealtimeBypassList;
historyBypassList = newHistoryBypassList;
log.info("[BypassTaskSwitchCache#init]:end");
} catch (Exception e) {
log.error("[BypassTaskSwitchCache#init] error", e);
umpAlarmService.businessAlarm("旁路[BypassTaskSwitchCache] 失败,请关注");
}
}
/**
* 任务执行( 1分钟执行 1次)
*/
@Scheduled(cron = "0 0/1 * * * ?")
public void refresh() {
log.info("[BypassTaskSwitchCache#refresh]:start");
init();
log.info("[BypassTaskSwitchCache#refresh]:end");
}
/**
* getBypassTaskMap
*
* @return bypassTaskMap
*/
@Override
public Map<String, BypassTaskConfigDto> getBypassTaskMap() {
return bypassTaskMap;
}
/**
* getRealtimeBypassList
*
* @return realtimeBypassList
*/
@Override
public List<BypassTaskConfigDto> getRealtimeBypassList() {
return realtimeBypassList;
}
/**
* getHistoryBypassList
*
* @return historyBypassList
*/
@Override
public List<BypassTaskConfigDto> getHistoryBypassList() {
return historyBypassList;
}
/**
* getBypassTaskByVersion
*
* @param version version
* @return BypassTaskConfigDto
*/
@Override
public BypassTaskConfigDto getBypassTaskByVersion(String version) {
Map<String, BypassTaskConfigDto> bypassTaskMap = getBypassTaskMap();
if (CollectionUtils.isEmpty(bypassTaskMap)) {
return null;
}
return bypassTaskMap.get(version);
}
/**
* 险种获取开关状态
* 1、判断开关状态
* 2、判断险种是否存在
*
* @param version 旁路版本
* @param insuranceId 险种Id
* @param insuranceType 子险种Id
* @return
*/
@Override
public boolean getSwitch(String version, String insuranceId, String insuranceType) {
BypassTaskConfigDto bypassTaskByVersion = getBypassTaskByVersion(version);
if (Objects.isNull(bypassTaskByVersion)) {
return false;
}
List<BypassInsuranceDto> insurances = bypassTaskByVersion.getInsurances();
return insurances.stream().anyMatch(item -> {
if (StringUtils.isNotBlank(item.getInsuranceType())){
return item.getInsuranceId().equals(insuranceId) && item.getInsuranceType().equals(insuranceType);
}
return item.getInsuranceId().equals(insuranceId);
});
}
/**
* 获取旁路数据
* @param version 版本号
* @param insuranceId 险种Id
* @param insuranceType 子险种Id
* @return
*/
@Override
public MerchantBypassDto getMerchantBypassDto(String version, String insuranceId, String insuranceType) {
BypassTaskConfigDto bypassTaskByVersion = getBypassTaskByVersion(version);
if (Objects.isNull(bypassTaskByVersion)) {
return null;
}
List<BypassInsuranceDto> insurances = bypassTaskByVersion.getInsurances();
boolean switchFlag = insurances.stream().anyMatch(item -> {
if (StringUtils.isNotBlank(item.getInsuranceType())) {
return item.getInsuranceId().equals(insuranceId) && item.getInsuranceType().equals(insuranceType);
}
return item.getInsuranceId().equals(insuranceId);
});
if (Boolean.FALSE.equals(switchFlag)) {
return null;
}
MerchantBypassDto merchantBypassDto = new MerchantBypassDto();
merchantBypassDto.setVersion(bypassTaskByVersion.getVersion());
merchantBypassDto.setBypassTaskBusinessConfigDto(bypassTaskByVersion.getBypassTaskBusinessConfig());
return merchantBypassDto;
}
}
1、HashMap
1)LRUCache
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* LRUCache
* 优点:简单粗暴,不需要引入第三方包,比较适合一些比较简单的场景。
* 缺点:没有缓存淘汰策略,定制化开发成本高。
*
* @author zhangyujin
* @date 2023/1/3 13:54.
*/
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
/**
* 可重入读写锁,保证并发读写安全性
*/
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* readLock
*/
private final Lock readLock = readWriteLock.readLock();
/**
* writeLock
*/
private final Lock writeLock = readWriteLock.writeLock();
/**
* 缓存大小限制
*/
private final int maxSize;
public LRUCache(int maxSize) {
super(maxSize + 1, 1.0f, true);
this.maxSize = maxSize;
}
/**
* @param key key
* @return Object
*/
@Override
public V get(Object key) {
readLock.lock();
try {
return super.get(key);
} finally {
readLock.unlock();
}
}
/**
* put
*
* @param key key
* @param value value
* @return Object
*/
@Override
public V put(K key, V value) {
writeLock.lock();
try {
return super.put(key, value);
} finally {
writeLock.unlock();
}
}
/**
* removeEldestEntry
*
* @param eldest eldest
* @return boolean
*/
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return this.size() > maxSize;
}
}
二、GuavaCache
1、GuavaCache-定时刷新
本方式适用于,缓存不经常变化,切已知要缓存的数据的情况(缓存不过期,通过定时进行刷新)
1)、AbstractLocalCacheManager
@Slf4j
@EnableScheduling
public abstract class AbstractLocalCacheManager<K, V> implements LocalCacheManage<K, V> {
/**
* IGNORE_kEY
*/
private Set<K> ignoreKey;
/**
* AbstractLocalCacheManager
*/
public AbstractLocalCacheManager() {
}
/**
* AbstractLocalCacheManager
*
* @param ignoreKey ignoreKey
*/
public AbstractLocalCacheManager(Set<K> ignoreKey) {
this.ignoreKey = ignoreKey;
}
/**
* 缓存容器
*/
private final Cache<String, V> localCache = CacheBuilder.newBuilder()
.maximumSize(200000)
.recordStats()
.build();
/**
* 初始化数据
*/
@PostConstruct
public void init() {
refreshAll();
}
/**
* 获取全部缓存信息
*
* @return Map<String, V>
*/
@Override
public Map<String, V> getAllLocalCache() {
ConcurrentMap<String, V> concurrentMap = localCache.asMap();
if (CollectionUtils.isEmpty(concurrentMap)) {
refreshAll();
}
return localCache.asMap();
}
/**
* 获取多个Keys结果
*
* @param keys keys
* @return Map<K, V>
*/
@Override
public Map<K, V> getLocalCache(Set<K> keys) {
if (CollectionUtils.isEmpty(keys)) {
return Collections.emptyMap();
}
if (!CollectionUtils.isEmpty(ignoreKey)) {
keys.removeAll(ignoreKey);
}
if (CollectionUtils.isEmpty(keys)) {
return Collections.emptyMap();
}
// 转成本地key
Map<String, K> localKeyMap = keys.stream().collect(Collectors.toMap(this::toLocalKey, k -> k));
// 先获取本地结果
Map<String, V> localResultMap = localCache.getAllPresent(localKeyMap.keySet());
// 获取除本地缓存之外的其余信息
Map<K, V> loadResultMap = Collections.emptyMap();
if (localKeyMap.size() > localResultMap.size()) {
Set<String> absentLocalKeys = Sets.difference(localKeyMap.keySet(), localResultMap.keySet());
Set<K> absentKeys = absentLocalKeys.stream().map(localKeyMap::get).collect(Collectors.toSet());
// 存在失效的key
if (!absentKeys.isEmpty()) {
loadResultMap = refresh(absentKeys);
}
}
// 合并结果
Map<K, V> retMap = new HashMap<>(loadResultMap);
localResultMap.forEach((localKey, v) -> {
K k = localKeyMap.get(localKey);
retMap.put(k, v);
});
return retMap;
}
/**
* 获取单个Key
*
* @param k k
* @return V
*/
public V getLocalCache(K k) {
if (k == null) {
return null;
}
if (!CollectionUtils.isEmpty(ignoreKey) && ignoreKey.contains(k)) {
return null;
}
String localKey = toLocalKey(k);
V ret = localCache.getIfPresent(localKey);
if (ret == null) {
return refresh(k);
}
return ret;
}
/**
* 重新加载Key,会对历史缓存进行清理
*
* @param key key
* @return @return
*/
public V refresh(K key) {
try {
V v = load(key);
localCache.invalidate(key);
if (v != null) {
writeCache(key, v);
}
return v;
} catch (Exception ex) {
String errorMsg = MessageFormatter.format("加载:{}.loadKey({})到内存-异常:", new Object[]{this.getClass().getSimpleName(), key}).getMessage();
log.error(errorMsg, ex);
throw new RuntimeException(errorMsg);
}
}
/**
* 重新加载多个Key,会对历史缓存进行清理
*
* @param keys keys
* @return Map<K, V>
*/
public Map<K, V> refresh(Set<K> keys) {
try {
Map<K, V> map = load(keys);
localCache.invalidateAll(keys);
if (!CollectionUtils.isEmpty(map)) {
map.forEach(this::writeCache);
}
return map;
} catch (Exception ex) {
String errorMsg = MessageFormatter.format("加载:{}.loadKeys({})到内存-异常:", new Object[]{this.getClass().getSimpleName(), JsonUtils.toJsonString(keys)}).getMessage();
log.error(errorMsg, ex);
throw new RuntimeException(errorMsg);
}
}
/**
* 刷新所有Key,会对历史缓存进行清理
*/
@Override
public void refreshAll() {
try {
long t1 = System.currentTimeMillis();
Map<K, V> map = loadAll();
localCache.invalidateAll();
if (!CollectionUtils.isEmpty(map)) {
map.forEach(this::writeCache);
}
log.info("加载:{}.loadAll() success, stats:{},cost:{}ms", this.getClass().getSimpleName(), localCache.stats(), System.currentTimeMillis() - t1);
} catch (Exception ex) {
String errorMsg = MessageFormatter.format("加载:{}.loadAll()到内存-异常:", new Object[]{this.getClass().getSimpleName()}).getMessage();
log.error(errorMsg, ex);
throw new RuntimeException(errorMsg);
}
}
/**
* 子类实现,缓存数据加载,单Key
*
* @param keys
* @return
*/
protected abstract Map<K, V> load(Set<K> keys);
/**
* 子类实现,缓存数据加载,多Key
*
* @param key
* @return
*/
protected abstract V load(K key);
/**
* 子类实现,缓存数据加载,全部Key
*
* @return Map<K, V>
*/
protected abstract Map<K, V> loadAll();
/**
* 缓存Key转换
*
* @param k k
* @return String
*/
private String toLocalKey(K k) {
return k.toString();
}
/**
* 写入缓存
*
* @param k k
* @param v v
*/
private void writeCache(K k, V v) {
String localKey = toLocalKey(k);
localCache.put(localKey, v);
}
/**
* 删除缓存key
*
* @param k key
*/
public void remove(K k) {
localCache.invalidate(k);
}
}
2)StringLocalCache
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
*
* StringLocalCache
* @author zhangyujin
* @date 2023/1/3 13:30.
*/
@Slf4j
@Component
public class StringLocalCache extends AbstractLocalCacheManager<String, List<String>> {
/**
* @param s s
* @return load
*/
@Override
protected List<String> load(String s) {
return null;
}
/**
* load
*
* @param keys keys
* @return Map<String, String>
*/
@Override
protected Map<String, List<String>> load(Set<String> keys) {
return null;
}
/**
* loadAll
*
* @return Map<String, String>
*/
@Override
protected Map<String, List<String>> loadAll() {
return null;
}
/**
* 每5分钟更新一次缓存
*/
@Scheduled(fixedRate = 5 * 60 * 1000)
@Override
public void refreshAll() {
super.refreshAll();
}
}
三、Caffeine
⬤
Guava
Cache
是由LRU
替换算法的缓存技术。但Guava
Cache
由于被下面即将介绍的Caffeine
全面超越而被取代,其和Caffeine
方法基本一致⬤ 通过对
guava
cache
和caffeine
从性能到算法及使用的对比中,可以发现Caffeine
基本是在Guava
的基础上进行优化而来的,提供的功能基本一致,但是通过对算法和部分逻辑的优化,完成了对性能极大的提升,而且我们可以发现,两者切换几乎没有成本,毕竟caffeine
就是以替换guava
cache
为目的而来的。⬤ 那么为什么这么好的东西需要被淘汰呢,如果对于本地
Cache
有过深入研究的人应该知道LRU
算法基本可以满足大部分的场景,但是很多人为了精益求精,基于LRU
的算法,又在此基础上提出了一系列更好的,更有效果的淘汰策略。比如有ARC
,LIRS
和W-TinyLFU
等都提供了接近最理想的命中率,他们这些算法进一步提高了本地缓存的效率 (Caffein
e 采用了一种结合LRU
、LFU
优点的算法:W-TinyLFU
,其特点是:高命中率、低内存占用。)注意:
⬤ 需要注意的是,在使用
Guava
的get()
方法时,当缓存的load()
方法返回null
时,会抛出ExecutionException
。切换到Caffeine
后,get()
方法不会抛出异常,允许返回为null
。
1、Caffeine
— 定时刷新
1)AbstractLocalCacheManager
@Slf4j
@EnableScheduling
public abstract class AbstractLocalCacheManager<K, V> implements LocalCacheManage<K, V> {
/**
* IGNORE_kEY
*/
private Set<K> ignoreKey;
/**
* AbstractLocalCacheManager
*/
public AbstractLocalCacheManager() {
}
/**
* AbstractLocalCacheManager
*
* @param ignoreKey ignoreKey
*/
public AbstractLocalCacheManager(Set<K> ignoreKey) {
this.ignoreKey = ignoreKey;
}
/**
* 缓存容器
*/
private final Cache<String, V> localCache = Caffeine.newBuilder()
.maximumSize(200000)
.recordStats()
.build();
/**
* 初始化数据
*/
@PostConstruct
public void init() {
refreshAll();
}
/**
* 获取全部缓存信息
*
* @return Map<String, V>
*/
@Override
public Map<String, V> getAllLocalCache() {
ConcurrentMap<String, V> concurrentMap = localCache.asMap();
if (CollectionUtils.isEmpty(concurrentMap)) {
refreshAll();
}
return localCache.asMap();
}
/**
* 获取多个Keys结果
*
* @param keys keys
* @return Map<K, V>
*/
@Override
public Map<K, V> getLocalCache(Set<K> keys) {
if (CollectionUtils.isEmpty(keys)) {
return Collections.emptyMap();
}
if (!CollectionUtils.isEmpty(ignoreKey)) {
keys.removeAll(ignoreKey);
}
if (CollectionUtils.isEmpty(keys)) {
return Collections.emptyMap();
}
// 转成本地key
Map<String, K> localKeyMap = keys.stream().collect(Collectors.toMap(this::toLocalKey, k -> k));
// 先获取本地结果
Map<String, V> localResultMap = localCache.getAllPresent(localKeyMap.keySet());
// 获取除本地缓存之外的其余信息
Map<K, V> loadResultMap = Collections.emptyMap();
if (localKeyMap.size() > localResultMap.size()) {
Set<String> absentLocalKeys = Sets.difference(localKeyMap.keySet(), localResultMap.keySet());
Set<K> absentKeys = absentLocalKeys.stream().map(localKeyMap::get).collect(Collectors.toSet());
// 存在失效的key
if (!absentKeys.isEmpty()) {
loadResultMap = refresh(absentKeys);
}
}
// 合并结果
Map<K, V> retMap = new HashMap<>(loadResultMap);
localResultMap.forEach((localKey, v) -> {
K k = localKeyMap.get(localKey);
retMap.put(k, v);
});
return retMap;
}
/**
* 获取单个Key
*
* @param k k
* @return V
*/
public V getLocalCache(K k) {
if (k == null) {
return null;
}
if (!CollectionUtils.isEmpty(ignoreKey) && ignoreKey.contains(k)) {
return null;
}
String localKey = toLocalKey(k);
V ret = localCache.getIfPresent(localKey);
if (ret == null) {
return refresh(k);
}
return ret;
}
/**
* 重新加载Key,会对历史缓存进行清理
*
* @param key key
* @return @return
*/
public V refresh(K key) {
try {
V v = load(key);
localCache.invalidate(key);
if (v != null) {
writeCache(key, v);
}
return v;
} catch (Exception ex) {
String errorMsg = MessageFormatter.format("加载:{}.loadKey({})到内存-异常:", new Object[]{this.getClass().getSimpleName(), key}).getMessage();
log.error(errorMsg, ex);
throw new RuntimeException(errorMsg);
}
}
/**
* 重新加载多个Key,会对历史缓存进行清理
*
* @param keys keys
* @return Map<K, V>
*/
public Map<K, V> refresh(Set<K> keys) {
try {
Map<K, V> map = load(keys);
localCache.invalidateAll(keys);
if (!CollectionUtils.isEmpty(map)) {
map.forEach(this::writeCache);
}
return map;
} catch (Exception ex) {
String errorMsg = MessageFormatter.format("加载:{}.loadKeys({})到内存-异常:", new Object[]{this.getClass().getSimpleName(), JsonUtils.toJsonString(keys)}).getMessage();
log.error(errorMsg, ex);
throw new RuntimeException(errorMsg);
}
}
/**
* 刷新所有Key,会对历史缓存进行清理
*/
@Override
public void refreshAll() {
try {
long t1 = System.currentTimeMillis();
Map<K, V> map = loadAll();
localCache.invalidateAll();
if (!CollectionUtils.isEmpty(map)) {
map.forEach(this::writeCache);
}
log.info("加载:{}.loadAll() success, stats:{},cost:{}ms", this.getClass().getSimpleName(), localCache.stats(), System.currentTimeMillis() - t1);
} catch (Exception ex) {
String errorMsg = MessageFormatter.format("加载:{}.loadAll()到内存-异常:", new Object[]{this.getClass().getSimpleName()}).getMessage();
log.error(errorMsg, ex);
throw new RuntimeException(errorMsg);
}
}
/**
* 子类实现,缓存数据加载,单Key
*
* @param keys
* @return
*/
protected abstract Map<K, V> load(Set<K> keys);
/**
* 子类实现,缓存数据加载,多Key
*
* @param key
* @return
*/
protected abstract V load(K key);
/**
* 子类实现,缓存数据加载,全部Key
*
* @return Map<K, V>
*/
protected abstract Map<K, V> loadAll();
/**
* 缓存Key转换
*
* @param k k
* @return String
*/
private String toLocalKey(K k) {
return k.toString();
}
/**
* 写入缓存
*
* @param k k
* @param v v
*/
private void writeCache(K k, V v) {
String localKey = toLocalKey(k);
localCache.put(localKey, v);
}
/**
* 删除缓存key
*
* @param k key
*/
public void remove(K k) {
localCache.invalidate(k);
}
}
2)StringLocalCache
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
*
* StringLocalCache
* @author zhangyujin
* @date 2023/1/3 13:30.
*/
@Slf4j
@Component
public class StringLocalCache extends AbstractLocalCacheManager<String, List<String>> {
/**
* @param s s
* @return load
*/
@Override
protected List<String> load(String s) {
return null;
}
/**
* load
*
* @param keys keys
* @return Map<String, String>
*/
@Override
protected Map<String, List<String>> load(Set<String> keys) {
return null;
}
/**
* loadAll
*
* @return Map<String, String>
*/
@Override
protected Map<String, List<String>> loadAll() {
return null;
}
/**
* 每5分钟更新一次缓存
*/
@Scheduled(fixedRate = 1 * 60 * 1000)
@Override
public void refreshAll() {
super.refreshAll();
}
}
三、多级缓存
⬤ 缓存,就是让数据更接近使用者,让访问速度加快,从而提升系统性能。工作机制大概是先从缓存中加载数据,如果没有,再从慢速设备(eg:数据库)中加载数据并同步到缓存中。
⬤ 多级缓存,是指在整个系统架构的不同系统层面进行数据缓存,以提升访问速度。主要分为三层缓存:网关
nginx
缓存、分布式缓存、本地缓存。这里的多级缓存就是用redis
分布式缓存 +caffeine
本地缓存整合而来。
平时我们在开发过程中,一般都是使用 redis
实现分布式缓存、caffeine
操作本地缓存,但是发现只使用 redis
或者是 caffeine
实现缓存都有一些问题:
⬤ 一级缓存:Caffeine
是一个一个高性能的 Java
缓存库;使用 Window
TinyLfu
回收策略,提供了一个近乎最佳的命中率。优点数据就在应用内存所以速度快。缺点受应用内存的限制,所以容量有限;没有持久化,重启服务后缓存数据会丢失;在分布式环境下缓存数据数据无法同步;
⬤ 二级缓存:redis
是一高性能、高可用的 key
- value
数据库,支持多种数据类型,支持集群,和应用服务器分开部署易于横向扩展。优点支持多种数据类型,扩容方便;有持久化,重启应用服务器缓存数据不会丢失;他是一个集中式缓存,不存在在应用服务器之间同步数据的问题。缺点每次都需要访问 redis
存在 IO
浪费的情况。
综上所述,我们可以通过整合 redis
和 caffeine
实现多级缓存,解决上面单一缓存的痛点,从而做到相互补足。
1、redis
和 caffeine
多级缓存
1)技术方案
四、缓存对象占用观察
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>8.11.2</version>
</dependency>
RamUsageEstimator就是根据java对象在堆内存中的存储格式,通过计算Java对象头、实例数据、引用等的大小,相加而得,如果有引用,还能递归计算引用对象的大小。RamUsageEstimator的源码并不多,几百行,清晰可读。这里不进行一一解读了。它在初始化的时候会根据当前JVM运行环境、CPU架构、运行参数、是否开启指针压缩、JDK版本等综合计算对象头的大小,而实例数据部分则按照java基础数据类型的标准大小进行计
使用该第三方工具比较简单直接,主要依靠JVM本身环境、参数及CPU架构计算头信息,再依据数据类型的标准计算实例字段大小,计算速度很快,另外使用较方便。如果非要说这种方式有什么缺点的话,那就是这种方式计算所得的对象头大小是基于JVM声明规范的,并不是通过运行时内存地址计算而得,存在与实际大小不符的这种可能性。
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
System.out.println("map init value is " + RamUsageEstimator.sizeOfObject(map));
for (int i = 1000000; i < 6000000; i++) {
map.put(String.valueOf(i), String.valueOf(i+1000000));
}
System.out.println("map size: 1 is " + RamUsageEstimator.sizeOfObject(map.get("1000000"))+ " byte");
System.out.println("map size: " +map.size() +" is " + RamUsageEstimator.sizeOfMap(map)+ " byte");
System.out.println("map size: " +map.size() +" is " + RamUsageEstimator.sizeOfObject(map)+ " byte");
// JDK自带
System.out.println("map size: " +map.size() +" is " + ObjectSizeCalculator.getObjectSize(map) + " byte");
}
map init value is 48
map size: 1 is 56 byte
map size: 5000000 is 720000048 byte
map size: 5000000 is 720000048 byte
map size: 5000000 is 753554512 byte