Java緩存數(shù)據(jù)讀取失?。罕苊忪o態(tài)變量和單例模式的陷阱
Java應(yīng)用中,緩存大量數(shù)據(jù)以提升性能是常見(jiàn)做法。然而,有時(shí)會(huì)遇到從緩存中讀取數(shù)據(jù)失敗的問(wèn)題。本文分析一個(gè)案例,探討導(dǎo)致Java緩存數(shù)據(jù)讀取失敗的原因,并提供解決方案。
案例:內(nèi)存不足導(dǎo)致緩存數(shù)據(jù)丟失
開(kāi)發(fā)者使用scenariobuffer類(lèi)將約16萬(wàn)條資產(chǎn)數(shù)據(jù)加載到名為assetbuffer的HashMap中。getbasset方法用于讀取緩存數(shù)據(jù)。在服務(wù)器內(nèi)存不足(可用內(nèi)存僅剩100MB,緩存占用3GB,總內(nèi)存8GB)時(shí),getbasset方法返回空值。重啟服務(wù)器并清除緩存后,問(wèn)題解決。項(xiàng)目使用tomcat啟動(dòng),分配約3GB內(nèi)存。代碼如下:
立即學(xué)習(xí)“Java免費(fèi)學(xué)習(xí)筆記(深入)”;
@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); } }
問(wèn)題分析與解決方案
問(wèn)題并非單純的內(nèi)存不足導(dǎo)致jvm清空數(shù)據(jù),而是代碼設(shè)計(jì)缺陷:
-
不當(dāng)使用static和getinstance(): scenariobuffer類(lèi)使用static關(guān)鍵字,使其成為單例。但getinstance()方法冗余,因?yàn)镾pring容器已保證@Component注解的Bean是單例的。static關(guān)鍵字導(dǎo)致assetbuffer成為靜態(tài)變量,生命周期與應(yīng)用服務(wù)器相同,即使JVM垃圾回收,也難以回收其內(nèi)存。
-
不推薦的Bean獲取方式: 使用SpringUtil.getBean(IAssetService.class)獲取Bean,不推薦。Spring框架推薦使用@Autowired或@Resource注解進(jìn)行依賴(lài)注入。
-
初始化時(shí)機(jī)問(wèn)題: 使用ApplicationRunner接口在應(yīng)用啟動(dòng)時(shí)初始化緩存,但更好的方式是使用@PostConstruct注解或?qū)崿F(xiàn)InitializingBean接口,使初始化過(guò)程更明確可控。
改進(jìn)后的代碼
修改scenariobuffer類(lèi)及其使用方法:
@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); } }
在其他服務(wù)類(lèi)中,使用@Resource注解注入scenariobuffer:
@Service public class XxxService { @Resource private ScenarioBuffer scenarioBuffer; public void xxx() { List<Asset> asset = scenarioBuffer.getBAsset("xxx"); } }
改進(jìn)后,避免了不必要的靜態(tài)變量和單例獲取方法,利用Spring框架的依賴(lài)注入機(jī)制和@PostConstruct注解,使代碼更清晰、易于維護(hù),并減少了內(nèi)存泄漏的風(fēng)險(xiǎn)。即使服務(wù)器內(nèi)存緊張,JVM垃圾回收機(jī)制也能更有效工作,降低緩存數(shù)據(jù)讀取失敗的可能性。