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

