最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

在Spring Boot中淺嘗內(nèi)存泄漏的實(shí)戰(zhàn)記錄

 更新時(shí)間:2025年04月17日 11:36:08   作者:江湖人稱-杰  
本文給大家分享在Spring Boot中淺嘗內(nèi)存泄漏的實(shí)戰(zhàn)記錄,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧

使用靜態(tài)集合持有對象引用,阻止GC回收

關(guān)鍵點(diǎn):

使用static List作為內(nèi)存泄漏的錨點(diǎn),其生命周期與ClassLoader一致
每次請求向列表添加1MB字節(jié)數(shù)組,這些對象會(huì)持續(xù)占用堆內(nèi)存
由于集合持有強(qiáng)引用,GC無法回收這些對象
最終會(huì)導(dǎo)致OutOfMemoryError: Java heap space

可執(zhí)行代碼:

package io.renren.controller;
import org.springframework.boot.SpringApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List;
/**
 * author: lj
 * date: 2025-4
 */
@RestController
public class MemoryLeakController {
    // 靜態(tài)集合會(huì)持續(xù)持有對象引用
    private static List<byte[]> LEAKING_LIST = new ArrayList<>();
    // 內(nèi)存泄漏端點(diǎn)
    @GetMapping("/leak")
    public String leakMemory() {
        // 每次請求添加1MB數(shù)據(jù)(不會(huì)被釋放)
        LEAKING_LIST.add(new byte[1024 * 1024]);
        return "已泄漏內(nèi)存: " + LEAKING_LIST.size() + " MB";
    }
    // 觸發(fā)OOM的測試方法(快速驗(yàn)證)
    public static void main(String[] args) throws InterruptedException {
        SpringApplication.run(MemoryLeakController.class, args);
        // 通過循環(huán)請求快速觸發(fā)OOM
        while(true) {
            new RestTemplate().getForObject("http://localhost:8080/leak", String.class);
            Thread.sleep(100);
        }
    }
}

驗(yàn)證:

1,運(yùn)行程序(啟動(dòng)時(shí)添加JVM參數(shù)限制堆大小):

//在cmd中先cd到j(luò)ar包所在目錄,執(zhí)行如下命令啟動(dòng)
//-Xmx100m 當(dāng)程序需要更多內(nèi)存時(shí),JVM會(huì)嘗試分配最多100MB的堆內(nèi)存。如果超過這個(gè)限制,可能會(huì)拋出OutOfMemoryError
//-Xms100m JVM在啟動(dòng)時(shí)分配的最小內(nèi)存量。如果初始堆內(nèi)存設(shè)置得過低,程序可能在運(yùn)行過程中頻繁擴(kuò)展堆內(nèi)存,影響性能。
//-XX:+HeapDumpOnOutOfMemoryError 在發(fā)生OutOfMemoryError時(shí)生成堆轉(zhuǎn)儲(chǔ)(Heap Dump)的功能
java -jar -Xmx100m -Xms100m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\Temp renren-generator-1.0.0.jar

2,訪問 http://localhost:8080/leak 觸發(fā)泄漏

請?zhí)砑訄D片描述

日志輸出顯示了內(nèi)存泄漏位置。

請?zhí)砑訄D片描述

并且在臨時(shí)目錄中保存了一份堆轉(zhuǎn)儲(chǔ)文件,稍后使用MAT(Memory Analyzer Tool)分析。

請?zhí)砑訄D片描述

問題定位

使用jvisualvm工具定位問題

在cmd輸入jvisualvm指令

請?zhí)砑訄D片描述

選中應(yīng)用后,可以監(jiān)控應(yīng)用程序的性能。

請?zhí)砑訄D片描述

觸發(fā)內(nèi)存泄露后,查看每次GC的持續(xù)時(shí)間、回收的內(nèi)存等信息。OOM之后,點(diǎn)擊界面右上角的堆Dump,打開應(yīng)用的堆轉(zhuǎn)儲(chǔ)信息。

請?zhí)砑訄D片描述

查找最大對象

請?zhí)砑訄D片描述

打開java.lang.Object[]的保留堆

請?zhí)砑訄D片描述

查看LEAKING_LIST的引用鏈,至此問題定位完成。

請?zhí)砑訄D片描述

使用MAT(Memory Analyzer Tool)工具定位問題

下載地址:https://eclipse.dev/mat/download/previous/
我的是JDK8,所以我下載了Memory Analyzer 1.10.0 Release版本。下載完成后,直接解壓,運(yùn)行其中的MemoryAnalyzer.exe文件即可啟動(dòng)MAT工具。

用mat工具打開剛剛臨時(shí)目錄中保存的堆轉(zhuǎn)儲(chǔ)文件,點(diǎn)擊Leak Suspects生成內(nèi)存泄漏報(bào)表。

請?zhí)砑訄D片描述

點(diǎn)擊details查看java.lang.Object[]的保留堆

請?zhí)砑訄D片描述

查看LEAKING_LIST的引用鏈,至此問題定位完成。

請?zhí)砑訄D片描述

調(diào)優(yōu)建議

1,避免長時(shí)間持有大對象引用。
2,定期執(zhí)行集合清理操作。

@Scheduled(fixedRate = 60_000)
public void cleanLeakingData() {
    LEAKING_LIST.removeIf(data -> /* 清理?xiàng)l件 */);
}

--------------------------------------------------更新---------------------------------------------------------

變種實(shí)現(xiàn)方式

@SpringBootApplication
@RestController
@EnableCaching // 關(guān)鍵注解:啟用緩存
public class CacheLeakDemo {
    // 模擬緩存未正確清理
    @Cacheable("leakyCache")
    @GetMapping("/cache-leak")
    public byte[] cacheLeak() {
        return new byte[1024 * 1024]; // 每次緩存1MB
    }
    public static void main(String[] args) {
        SpringApplication.run(CacheLeakDemo.class, args);
    }
}

緩存泄漏原理:
@Cacheable會(huì)將每次不同參數(shù)的返回結(jié)果緩存

因?yàn)闆]有設(shè)置過期時(shí)間或大小限制,緩存會(huì)無限增長

示例中每個(gè)請求生成唯一key(默認(rèn)基于方法參數(shù)),導(dǎo)致緩存不斷累積

調(diào)優(yōu)建議

對于緩存使用WeakReference或框架(Caffeine/Ehcache)

// 使用WeakHashMap解決
private static Map<byte[], Boolean> SAFE_MAP = 
    Collections.synchronizedMap(new WeakHashMap<>());
// 使用Caffeine緩存并設(shè)置上限
@Bean
public CacheManager cacheManager() {
    CaffeineCacheManager manager = new CaffeineCacheManager();
    manager.setCaffeine(Caffeine.newBuilder()
            .maximumSize(100)
            .expireAfterWrite(10, TimeUnit.MINUTES));
    return manager;
}

因?yàn)樵?Java 中,WeakHashMap 的設(shè)計(jì)目的就是通過弱引用(Weak Reference)自動(dòng)清理不再被使用的鍵值對,從而避免因?qū)ο髿埩魧?dǎo)致的內(nèi)存泄漏。

引用類型對比表:

引用類型GC行為典型應(yīng)用場景
強(qiáng)引用永不回收(除非顯式置為null)普通對象引用
軟引用內(nèi)存不足時(shí)回收緩存
弱引用下次GC立即回收WeakHashMap/WeakReference
虛引用回收時(shí)收到通知資源清理跟蹤

關(guān)鍵機(jī)制:
WeakHashMap 的 鍵(Key)使用弱引用存儲(chǔ)
當(dāng)鍵對象不再被其他強(qiáng)引用持有時(shí),該鍵值對會(huì)被自動(dòng)移除
值對象(Value)仍使用強(qiáng)引用,需要特別注意解耦

內(nèi)存泄漏場景 vs WeakHashMap修復(fù)方案

//使用普通HashMap導(dǎo)致泄漏
public class LeakingCache {
    private static Map<byte[], String> CACHE = new HashMap<>();
    // 添加大對象到緩存
    public static void addToCache(byte[] key, String value) {
        CACHE.put(key, value);
    }
    public static void main(String[] args) {
        // 模擬添加后不再使用key
        byte[] key = new byte[1024 * 1024]; // 1MB
        addToCache(key, "大數(shù)據(jù)");
        key = null; // 刪除強(qiáng)引用
        // 觸發(fā)GC
        System.gc();
        // 緩存仍然持有key的強(qiáng)引用,導(dǎo)致1MB內(nèi)存無法回收
        System.out.println("緩存大小: " + CACHE.size()); // 輸出1
    }
}
//使用WeakHashMap
public class SafeCache {
    // 使用WeakHashMap + 同步包裝(線程安全)
    private static Map<byte[], String> SAFE_CACHE = 
        Collections.synchronizedMap(new WeakHashMap<>());
    public static void addToCache(byte[] key, String value) {
        SAFE_CACHE.put(key, value);
    }
    public static void main(String[] args) {
        byte[] key = new byte[1024 * 1024];
        addToCache(key, "安全數(shù)據(jù)");
        key = null; // 刪除最后一個(gè)強(qiáng)引用
        // 強(qiáng)制GC(生產(chǎn)環(huán)境不要主動(dòng)調(diào)用System.gc())
        System.gc();
        // 給GC一點(diǎn)時(shí)間執(zhí)行
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        System.out.println("緩存大小: " + SAFE_CACHE.size()); // 輸出0
    }
}

實(shí)戰(zhàn)應(yīng)用

場景:設(shè)備連接會(huì)話管理

@RestController
public class DeviceController {
    // 使用WeakHashMap管理臨時(shí)會(huì)話
    private static Map<Device, Session> deviceSessions = 
        Collections.synchronizedMap(new WeakHashMap<>());
    @PostMapping("/connect")
    public String connect(@RequestBody Device device) {
        Session session = new Session(device);
        deviceSessions.put(device, session);
        return "Connected";
    }
    // 當(dāng)Device對象不再被外部引用時(shí),自動(dòng)清理會(huì)話
}

配置驗(yàn)證端點(diǎn)

@GetMapping("/session-count")
public int getSessionCount() {
    return deviceSessions.size();
}

測試方法

1,發(fā)送連接請求
curl -X POST http://localhost:8080/connect -d '{"id":"device1"}'
2,立即調(diào)用/session-count查看數(shù)量
3,停止持有Device對象引用后觸發(fā)GC
4,再次檢查會(huì)話數(shù)量

增強(qiáng)版緩存實(shí)現(xiàn)(帶自動(dòng)清理)

public class AdvancedCache<K, V> {
    private final Map<K, V> cache = 
        new WeakHashMap<>();
    private final ReferenceQueue<K> queue = 
        new ReferenceQueue<>();
    public void put(K key, V value) {
        // 清理已回收的條目
        processQueue();
        cache.put(key, value);
    }
    private void processQueue() {
        Reference<? extends K> ref;
        while ((ref = queue.poll()) != null) {
            // 這里可以觸發(fā)回調(diào)清理相關(guān)資源
            System.out.println("清理?xiàng)l目: " + ref);
        }
    }
}

代碼測試片段

// 測試插入100萬條數(shù)據(jù)
IntStream.range(0, 1_000_000).forEach(i -> {
    Object key = new Object();
    map.put(key, "Value-" + i);
});
// 強(qiáng)制GC后統(tǒng)計(jì)剩余條目
System.gc();
Thread.sleep(1000);
System.out.println("剩余條目: " + map.size());

測試結(jié)果:

Map類型初始條目GC后剩余條目內(nèi)存占用(MB)
HashMap1,000,0001,000,00085.3
WeakHashMap1,000,0003,2146.7

場景:設(shè)備狀態(tài)臨時(shí)緩存

public class DeviceStateManager {
    // Key: 設(shè)備對象,Value: 最后上報(bào)時(shí)間
    private final WeakHashMap<Device, Long> lastReportTime = 
        new WeakHashMap<>();
    // 更新狀態(tài)
    public void updateState(Device device) {
        lastReportTime.put(device, System.currentTimeMillis());
    }
    // 獲取在線設(shè)備列表(需配合ReferenceQueue清理)
    public List<Device> getOnlineDevices() {
        return new ArrayList<>(lastReportTime.keySet());
    }
}

優(yōu)勢分析:
當(dāng)設(shè)備斷開連接且不再被其他模塊引用時(shí),自動(dòng)清理狀態(tài)
避免因設(shè)備頻繁上下線導(dǎo)致的內(nèi)存增長
適合作為二級(jí)緩存,配合持久化存儲(chǔ)使用

綜上:
WeakHashMap 是解決特定類型內(nèi)存泄漏的有效工具,但需要充分理解其工作原理和適用場景。在實(shí)際物聯(lián)網(wǎng)系統(tǒng)中,通常需要結(jié)合軟引用、引用隊(duì)列等機(jī)制構(gòu)建更健壯的緩存系統(tǒng)。

----------------------------------------------基礎(chǔ)信息補(bǔ)充--------------------------------------------------------
除了上方方法,也能通過JDK自帶的工具jmap,jconsole來獲得一個(gè)堆轉(zhuǎn)儲(chǔ)文件。

jvm(java虛擬機(jī))管理的內(nèi)存大致包括三種不同類型的內(nèi)存區(qū)域:

PermanentGeneration space(永久保存區(qū)域)、Heap space(堆區(qū)域)、JavaStacks(Java棧)。
1,其中永久保存區(qū)域主要存放Class(類)和Meta的信息,Class第一次被Load的時(shí)候被放入PermGenspace區(qū)域,Class需要存儲(chǔ)的內(nèi)容主要包括方法和靜態(tài)屬性。
2,堆區(qū)域用來存放Class的實(shí)例(即對象),對象需要存儲(chǔ)的內(nèi)容主要是非靜態(tài)屬性。每次用new創(chuàng)建一個(gè)對象實(shí)例后,對象實(shí)例存儲(chǔ)在堆區(qū)域中,這部分空間也被jvm的垃圾回收機(jī)制管理。
3,而Java棧跟大多數(shù)編程語言包括匯編語言的棧功能相似,主要基本類型變量以及方法的輸入輸出參數(shù)。Java程序的每個(gè)線程中都有一個(gè)獨(dú)立的堆棧。
容易發(fā)生內(nèi)存溢出問題的內(nèi)存空間包括:PermanentGeneration space和Heap space。

第一種OutOfMemoryError:PermGenspace

發(fā)生這種問題的原意是程序中使用了大量的jar或class,使java虛擬機(jī)裝載類的空間不夠,與PermanentGeneration space有關(guān)。解決這類問題有以下兩種辦法:

1、增加java虛擬機(jī)中的XX:PermSize和XX:MaxPermSize參數(shù)的大小,其中XX:PermSize是初始永久保存區(qū)域大小,XX:MaxPermSize是最大永久保存區(qū)域大小。如針對tomcat,在catalina.sh或catalina.bat文件中一系列環(huán)境變量名說明結(jié)束處(大約在70行左右) 增加一行:

JAVA_OPTS=" -XX:PermSize=64M -XX:MaxPermSize=128m"

第二種OutOfMemoryError:Java heap space

發(fā)生這種問題的原因是java虛擬機(jī)創(chuàng)建的對象太多,在進(jìn)行垃圾回收之間,虛擬機(jī)分配的到堆內(nèi)存空間已經(jīng)用滿了,與Heapspace有關(guān)。解決這類問題有兩種思路:

1、檢查程序,看是否有死循環(huán)或不必要地重復(fù)創(chuàng)建大量對象。找到原因后,修改程序和算法。

2、增加Java虛擬機(jī)中Xms(初始堆大小)和Xmx(最大堆大?。﹨?shù)的大小。如:set JAVA_OPTS= -Xms256m-Xmx1024m

第三種OutOfMemoryError:unable to create new nativethread

這種錯(cuò)誤在Java線程個(gè)數(shù)很多的情況下容易發(fā)生

GC

垃圾收集(GC)是Java內(nèi)存管理的重要機(jī)制之一。它負(fù)責(zé)自動(dòng)回收不再使用的對象所占用的內(nèi)存,以避免內(nèi)存泄漏和OOM問題的發(fā)生。
GC的工作原理主要涉及到兩個(gè)關(guān)鍵概念:標(biāo)記-清除(Mark-Sweep)和分代收集(Generational)。標(biāo)記-清除算法會(huì)遍歷整個(gè)堆空間,標(biāo)記出仍然被引用的對象,然后清除未被標(biāo)記的對象所占用的內(nèi)存。分代收集則是將堆空間劃分為新生代和老年代兩個(gè)區(qū)域,根據(jù)對象的存活周期采用不同的回收策略。

到此這篇關(guān)于在Spring Boot中淺嘗內(nèi)存泄漏的實(shí)戰(zhàn)記錄的文章就介紹到這了,更多相關(guān)Spring Boot內(nèi)存泄漏內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用Log4j2代碼方式配置實(shí)現(xiàn)線程級(jí)動(dòng)態(tài)控制

    使用Log4j2代碼方式配置實(shí)現(xiàn)線程級(jí)動(dòng)態(tài)控制

    這篇文章主要介紹了使用Log4j2代碼方式配置實(shí)現(xiàn)線程級(jí)動(dòng)態(tài)控制,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • 常用數(shù)據(jù)庫的驅(qū)動(dòng)程序及JDBC URL分享

    常用數(shù)據(jù)庫的驅(qū)動(dòng)程序及JDBC URL分享

    這篇文章主要介紹了常用數(shù)據(jù)庫的驅(qū)動(dòng)程序及 JDBC URL,需要的朋友可以看下
    2014-01-01
  • Mybatis 簡介與原理

    Mybatis 簡介與原理

    MyBatis 是支持定制化 SQL、存儲(chǔ)過程以及高級(jí)映射的優(yōu)秀的持久層框架。MyBatis 避免了幾乎所有的 JDBC 代碼和手動(dòng)設(shè)置參數(shù)以及獲取結(jié)果集
    2017-05-05
  • springboot集成redis存對象亂碼的問題及解決

    springboot集成redis存對象亂碼的問題及解決

    這篇文章主要介紹了springboot集成redis存對象亂碼的問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • IDEA中l(wèi)og4j 無法輸出到本地 properties配置無效問題

    IDEA中l(wèi)og4j 無法輸出到本地 properties配置無效問題

    這篇文章主要介紹了IDEA中l(wèi)og4j 無法輸出到本地 properties配置無效問題,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-10-10
  • java中String.matches方法使用

    java中String.matches方法使用

    String.matches()方法用于檢測字符串是否符合特定的正則表達(dá)式,詳細(xì)介紹了如何使用String.matches()配合不同的正則表達(dá)式來匹配各種特定格式的字符串,感興趣的可以了解一下
    2024-09-09
  • Java中Druid連接池連接超時(shí)獲取不到連接的解決

    Java中Druid連接池連接超時(shí)獲取不到連接的解決

    這篇文章主要介紹了Java中Druid連接池連接超時(shí)獲取不到連接的解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • Mybatis給數(shù)據(jù)庫敏感字段加解密詳解

    Mybatis給數(shù)據(jù)庫敏感字段加解密詳解

    這篇文章主要介紹了Mybatis給數(shù)據(jù)庫敏感字段加解密詳解,為了保護(hù)數(shù)據(jù)庫敏感字段數(shù)據(jù)安全,有時(shí)候我們需要將敏感數(shù)據(jù)加密入庫,查詢時(shí)再解密成明文,我們可以利用Mybatis自定義TypeHandler來處理,需要的朋友可以參考下
    2023-11-11
  • Java多線程局域網(wǎng)聊天室的實(shí)現(xiàn)

    Java多線程局域網(wǎng)聊天室的實(shí)現(xiàn)

    在學(xué)習(xí)了一個(gè)學(xué)期的java以后,搞了一個(gè)多線程的聊天室,熟悉了一下服務(wù)器和客戶機(jī)的操作。感興趣的小伙伴們可以參考一下
    2021-06-06
  • 詳解Java如何使用集合來實(shí)現(xiàn)一個(gè)客戶信息管理系統(tǒng)

    詳解Java如何使用集合來實(shí)現(xiàn)一個(gè)客戶信息管理系統(tǒng)

    讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用Java 集合實(shí)現(xiàn)一個(gè)客戶信息管理系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平
    2021-11-11

最新評論

长寿区| 泗洪县| 临泉县| 洪湖市| 泰来县| 体育| 象山县| 丰城市| 台山市| 乌拉特前旗| 新蔡县| 禄丰县| 泸州市| 义马市| 长岭县| 广东省| 尼勒克县| 睢宁县| 筠连县| 卓资县| 洛浦县| 潞西市| 会昌县| 西安市| 靖远县| 江陵县| 府谷县| 霍邱县| 尼勒克县| 革吉县| 辉县市| 塔河县| 招远市| 阳山县| 惠来县| 海安县| 佛冈县| 樟树市| 莫力| 巴塘县| 阿尔山市|