java內(nèi)存占用高案例
?? 案例1:靜態(tài)集合濫用(緩存無限增長)
場景:電商系統(tǒng)用靜態(tài) HashMap緩存用戶會話數(shù)據(jù),但未清理過期會話。
現(xiàn)象:內(nèi)存持續(xù)增長,頻繁 Full GC 后 Old 區(qū)內(nèi)存不釋放,最終 OOM。
代碼示例:
public class SessionManager {
private static Map<String, UserSession> sessions = new HashMap<>(); // 靜態(tài)Map未清理
public static void addSession(String id, UserSession session) {
sessions.put(id, session);
}
}原理:靜態(tài)集合生命周期與 JVM 一致,所有緩存對象無法被回收。
修復(fù)方案:
- 改用
WeakHashMap或Caffeine緩存框架,自動淘汰過期數(shù)據(jù); - 添加定時(shí)清理線程(例:每小時(shí)移除過期會話)。
?? 案例2:未關(guān)閉資源(文件流/數(shù)據(jù)庫連接)
場景:高頻讀取文件時(shí)忘記關(guān)閉流,或數(shù)據(jù)庫連接未歸還連接池。
現(xiàn)象:堆內(nèi)存緩慢上升,同時(shí)系統(tǒng)句柄數(shù)耗盡(too many open files)。
代碼示例:
public void readFile() throws IOException {
FileInputStream fis = new FileInputStream("large.txt"); // 未關(guān)閉!
byte[] data = fis.readAllBytes(); // 大文件直接加載到堆內(nèi)存
}原理:未關(guān)閉的流會占用堆外內(nèi)存(如文件描述符),同時(shí) byte[]對象堆積在堆內(nèi)。
修復(fù)方案:
- 必須用
try-with-resources自動關(guān)閉資源:
try (FileInputStream fis = new FileInputStream("large.txt")) {
// 使用資源
}- 連接池配置超時(shí)自動回收(如
maxIdleTime)。
?? 案例3:監(jiān)聽器未注銷(事件回調(diào)堆積)
場景:GUI 程序或消息系統(tǒng)中,監(jiān)聽器注冊后未移除。
現(xiàn)象:內(nèi)存緩慢增長,Old 區(qū)存在大量 EventListener對象。
代碼示例:
public class NotificationService {
private List<EventListener> listeners = new ArrayList<>();
public void addListener(EventListener listener) {
listeners.add(listener); // 添加后未提供移除方法
}
}原理:監(jiān)聽器持有業(yè)務(wù)對象引用(如用戶實(shí)例),即使業(yè)務(wù)對象已失效也無法回收。
修復(fù)方案:
- 提供
removeListener()方法并在對象銷毀時(shí)調(diào)用; - 使用
WeakReference包裝監(jiān)聽器,避免強(qiáng)引用阻塞回收。
?? 案例4:單例模式持有外部引用
場景:單例對象引用了短生命周期對象(如 Activity)。
現(xiàn)象:Android 應(yīng)用卡頓,后臺內(nèi)存居高不下。
代碼示例:
public class AppConfig {
private static AppConfig instance;
private Context context; // 持有Activity引用
private AppConfig(Context context) {
this.context = context; // 錯誤:Activity銷毀后單例仍持有其引用
}
public static AppConfig getInstance(Context context) {
if (instance == null) {
instance = new AppConfig(context);
}
return instance;
}
}原理:單例生命周期 = 應(yīng)用生命周期,其持有的 Context即使失效也無法回收。
修復(fù)方案:
- 用
Application Context代替Activity Context; - 對短生命周期對象使用弱引用:
WeakReference<Context>。
?? 案例5:ThreadLocal 誤用(線程池場景)
場景:線程池任務(wù)中使用 ThreadLocal后未清理。
現(xiàn)象:線程復(fù)用導(dǎo)致 ThreadLocal數(shù)據(jù)堆積,Old 區(qū)內(nèi)存階梯式上升。
代碼示例:
private static ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
executor.submit(() -> {
threadLocal.set(new byte[10 * 1024 * 1024]); // 10MB大對象
// 任務(wù)結(jié)束未調(diào)用 threadLocal.remove()
});原理:線程池復(fù)用線程時(shí),ThreadLocal上次設(shè)置的值未被清除,持續(xù)占用內(nèi)存。
修復(fù)方案:
- 必須在
finally塊中清理:
try {
threadLocal.set(data);
// 業(yè)務(wù)邏輯
} finally {
threadLocal.remove(); // 強(qiáng)制清理
}- 避免
static + ThreadLocal組合。
?? 案例6:匿名內(nèi)部類隱式引用
場景:非靜態(tài)內(nèi)部類(如 Handler/Runnable)持有外部類引用。
現(xiàn)象:Android 頁面關(guān)閉后內(nèi)存不釋放。
代碼示例:
public class MainActivity extends Activity {
void startTask() {
new Thread(() -> {
// 匿名內(nèi)部類隱式持有MainActivity引用
System.out.println(MainActivity.this);
}).start();
}
}原理:非靜態(tài)內(nèi)部類自動持有外部類實(shí)例,導(dǎo)致外部類無法回收。
修復(fù)方案:
- 改用 靜態(tài)內(nèi)部類 + 弱引用:
private static class MyTask implements Runnable {
private WeakReference<Activity> weakRef;
MyTask(Activity activity) {
weakRef = new WeakReference<>(activity);
}
@Override public void run() {
Activity activity = weakRef.get();
if (activity != null) { /* ... */ }
}
}?? 總結(jié):內(nèi)存升高根因速查表
場景 | 內(nèi)存升高特征 | 排查線索 | 修復(fù)關(guān)鍵 |
靜態(tài)集合濫用 | Old區(qū)持續(xù)增長,F(xiàn)ull GC無效 | MAT中HashMap$Node占比高 | 改用弱引用緩存或定時(shí)清理 |
未關(guān)閉資源 | 堆外內(nèi)存+堆內(nèi)byte[]同步增長 | 系統(tǒng)句柄數(shù)超標(biāo) + DirectBuffer高 | try-with-resources自動關(guān)閉 |
監(jiān)聽器未注銷 | 監(jiān)聽器對象堆積在Old區(qū) | 事件源類持有大量EventListener | 顯式調(diào)用removeListener() |
單例持有外部引用 | 單例關(guān)聯(lián)對象無法回收 | 單例字段引用短生命周期對象 | 替換為WeakReference |
ThreadLocal誤用 | 線程復(fù)用導(dǎo)致數(shù)據(jù)殘留 | ThreadLocalMap中值對象堆積 | finally塊中強(qiáng)制remove() |
匿名內(nèi)部類 | 外部類無法回收 | GC Root包含內(nèi)部類引用鏈 | 靜態(tài)內(nèi)部類+弱引用包裝 |
?? 預(yù)防建議:
- 代碼層面:避免 static濫用,所有資源操作必須配套關(guān)閉邏輯;
- 工具層面:集成 Arthas實(shí)時(shí)監(jiān)控內(nèi)存,壓測后用 MAT分析堆轉(zhuǎn)儲。
到此這篇關(guān)于java內(nèi)存占用高案例的文章就介紹到這了,更多相關(guān)java內(nèi)存占用高內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatis中的兩種參數(shù)傳遞類型詳解(示例代碼)
文章介紹了MyBatis中傳遞多個(gè)參數(shù)的兩種方式,使用Map和使用@Param注解或封裝POJO,Map方式適用于動態(tài)、不固定的參數(shù),但可讀性和安全性較差,@Param注解或POJO方式類型安全、可讀性強(qiáng),具體哪種方式好用,一起跟隨小編學(xué)習(xí)下吧2025-12-12
springboot starter介紹與自定義starter示例詳解
Spring Boot 的 Starter 是一組比較方便的依賴描述符,可以通過 Maven 將其打成jar包,并在你的項(xiàng)目中直接引用,這篇文章主要介紹了springboot starter介紹與自定義starter示例,需要的朋友可以參考下2025-05-05
Eclipse設(shè)定文件的默認(rèn)打開方式的具體操作步驟
以下是對Eclipse設(shè)定文件的默認(rèn)打開方式的具體操作步驟進(jìn)行了詳細(xì)的介紹,需要的朋友可以過來參考下2013-08-08
Java基于NIO實(shí)現(xiàn)群聊系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java基于NIO實(shí)現(xiàn)群聊系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11

