Java緩存數據讀取失敗:避免靜態變量和單例模式的陷阱
Java應用中,緩存大量數據以提升性能是常見做法。然而,有時會遇到從緩存中讀取數據失敗的問題。本文分析一個案例,探討導致Java緩存數據讀取失敗的原因,并提供解決方案。
案例:內存不足導致緩存數據丟失
開發者使用scenariobuffer類將約16萬條資產數據加載到名為assetbuffer的HashMap中。getbasset方法用于讀取緩存數據。在服務器內存不足(可用內存僅剩100MB,緩存占用3GB,總內存8GB)時,getbasset方法返回空值。重啟服務器并清除緩存后,問題解決。項目使用tomcat啟動,分配約3GB內存。代碼如下:
立即學習“Java免費學習筆記(深入)”;
@Component @Order(1) @Slf4j public class scenariobuffer implements IActionListener, ApplicationRunner { private Static Map<String, List<Asset>> assetbuffer = Collections.synchronizedMap(new HashMap<>()); private static scenariobuffer instance = new scenariobuffer(); public static scenariobuffer getinstance() { return instance; } public static List<Asset> getbasset(String groupid) { if (assetbuffer.containsKey(groupid)) { return assetbuffer.get(groupid); } return null; } @Override public void run(ApplicationArguments args) throws Exception { IAssetService assetService = springUtil.getBean(IAssetService.class); List<Asset> assetlist = assetService.list(); assetbuffer.put("key", assetlist); } }
問題分析與解決方案
問題并非單純的內存不足導致jvm清空數據,而是代碼設計缺陷:
-
不當使用static和getinstance(): scenariobuffer類使用static關鍵字,使其成為單例。但getinstance()方法冗余,因為Spring容器已保證@Component注解的Bean是單例的。static關鍵字導致assetbuffer成為靜態變量,生命周期與應用服務器相同,即使JVM垃圾回收,也難以回收其內存。
-
不推薦的Bean獲取方式: 使用SpringUtil.getBean(IAssetService.class)獲取Bean,不推薦。Spring框架推薦使用@Autowired或@Resource注解進行依賴注入。
-
初始化時機問題: 使用ApplicationRunner接口在應用啟動時初始化緩存,但更好的方式是使用@PostConstruct注解或實現InitializingBean接口,使初始化過程更明確可控。
改進后的代碼
修改scenariobuffer類及其使用方法:
@Component public class scenariobuffer implements IActionListener { private Map<String, List<Asset>> assetbuffer = new HashMap<>(); @Autowired private IAssetService assetservice; @PostConstruct public void init() { List<Asset> assetlist = assetservice.list(); assetbuffer.put("key", assetlist); } public List<Asset> getbasset(String groupid) { return assetbuffer.get(groupid); } }
在其他服務類中,使用@Resource注解注入scenariobuffer:
@Service public class XxxService { @Resource private ScenarioBuffer scenarioBuffer; public void xxx() { List<Asset> asset = scenarioBuffer.getBAsset("xxx"); } }
改進后,避免了不必要的靜態變量和單例獲取方法,利用Spring框架的依賴注入機制和@PostConstruct注解,使代碼更清晰、易于維護,并減少了內存泄漏的風險。即使服務器內存緊張,JVM垃圾回收機制也能更有效工作,降低緩存數據讀取失敗的可能性。