成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一篇文章帶你全面了解內(nèi)存泄漏

開發(fā) 開發(fā)工具
如果內(nèi)存泄漏已經(jīng)發(fā)生,在尋找內(nèi)存泄漏的問題點時,除了全面定點排查項目中涉及到資源使用的情況之外,還可以結合具體的編程語言(比如,Java的VisualVM等)的內(nèi)存分析工具進行來定位導致內(nèi)存泄漏的地方。

背景

今天這篇文章跟大家聊聊應用程序內(nèi)存泄漏相關的概念、原因以及排查和解決方案。

過完春節(jié)來公司,發(fā)現(xiàn)有幾個項目出現(xiàn)了很明顯的內(nèi)存泄漏問題。在此之前,一直在趕新功能的開發(fā),項目幾乎每天都在上線發(fā)布新的功能,內(nèi)存泄漏的問題并沒有暴露出來。春節(jié)期間,項目停止了發(fā)布,這一問題便顯現(xiàn)出來了。

項目是基于k8s部署的,有兩個項目的Pod進行了自動擴容,查看Pod的內(nèi)存使用情況,呈直線上升的趨勢。

內(nèi)存泄露場景圖內(nèi)存泄露場景圖

于是,節(jié)后的第一件事便是進行內(nèi)存泄漏問題的排查。項目中內(nèi)存泄漏的問題最終找到并解決了,在此期間也調(diào)研和排查了各類內(nèi)存泄漏的問題。本篇文章會對解決內(nèi)存泄漏問題中涉及到的理論知識進行梳理和講解,以便大家在遇到類似問題時可參考解決。

內(nèi)存泄漏與內(nèi)存溢出

在聊內(nèi)存泄漏的時候,肯定要提一下內(nèi)存溢出,這兩者很容易混淆,但區(qū)分缺失非常明顯的。

內(nèi)存溢出(Out of Memory,簡稱OOM),通俗地來講,就是當程序申請內(nèi)存時,沒有足夠的內(nèi)存可以使用了,也就是說程序申請的內(nèi)存大于系統(tǒng)能夠提供的內(nèi)存,此時就會出現(xiàn)Out Of Memory的錯誤。

內(nèi)存泄漏(Memory Leak),是指程序在申請內(nèi)存后,使用完畢之后,無法釋放對應的內(nèi)存空間。比如,在程序運行時,申請分配一部分內(nèi)存給臨時變量使用,但使用完之后這部分內(nèi)存沒有被手動釋放或無法被GC(Java中的垃圾回收)回收,就會導致此部分內(nèi)存始終被占用,從而導致內(nèi)存泄漏。

一次內(nèi)存泄漏危害可以忽略,但內(nèi)存泄漏堆積后果很嚴重,因為無論多少內(nèi)存,遲早會被耗光。最終導致OOM(內(nèi)存溢出)。

在Linux內(nèi)核的操作系統(tǒng)中,當系統(tǒng)內(nèi)存嚴重不足時,還會觸發(fā)OOM Killer(Out of Memory Killer)機制,強行釋放進程內(nèi)存。這也是某些應用程序莫名其妙被Kill的原因之一。

內(nèi)存泄漏分類

了解了內(nèi)存泄漏的基本定義,再來看看內(nèi)存泄漏的場景和分類。

按泄漏頻次分類

如果按照泄漏的頻次特性來劃分,內(nèi)存泄漏可分為4類:

常發(fā)性內(nèi)存泄漏:發(fā)生內(nèi)存泄漏的代碼經(jīng)常會被執(zhí)行,而每次執(zhí)行都會導致一定程度的內(nèi)存泄漏。

偶發(fā)性內(nèi)存泄漏:在某些特定環(huán)境或特定分支邏輯中才會發(fā)生內(nèi)存泄漏。常發(fā)性和偶發(fā)性是相對的。對于特定的環(huán)境,偶發(fā)性的也許就變成了常發(fā)性的。因此,測試環(huán)境和測試方法對檢測內(nèi)存泄漏至關重要。

一次性內(nèi)存泄漏:發(fā)生內(nèi)存泄漏的代碼只會被執(zhí)行一次,或者由于算法上的缺陷,導致總會有且僅有一塊內(nèi)存發(fā)生泄漏。比如,在類的構造函數(shù)中分配內(nèi)存,在析構函數(shù)中卻沒有釋放該內(nèi)存,此時內(nèi)存泄漏只會發(fā)生一次。

隱式內(nèi)存泄漏:程序在運行過程中不停地分配內(nèi)存,直到結束時才釋放。嚴格說這里并沒有發(fā)生內(nèi)存泄漏,因為內(nèi)存最終被釋放了。但是對于服務器程序來說,往往會運行幾天,幾周甚至幾個月,不及時釋放內(nèi)存也可能會導致耗盡系統(tǒng)的內(nèi)存。稱這類內(nèi)存泄漏為隱式內(nèi)存泄漏。

站在用戶的角度來看,內(nèi)存泄漏的影響有限(可能會產(chǎn)生響應慢等情況),但當內(nèi)存泄漏堆積到一定程度,耗盡系統(tǒng)內(nèi)存時,往往會導致服務器資源的浪費(比如,開篇提到的自動擴容)、響應緩慢,甚至OOM和OOM Killer。此時,危害性就比較大了。特別是應用系統(tǒng)沒有做自動擴容恢復等運維措施時。

對于上述4類內(nèi)存泄漏,常發(fā)性內(nèi)存泄漏最容易發(fā)現(xiàn)和解決,偶發(fā)性內(nèi)存泄漏次之,最難發(fā)現(xiàn)和排查的當屬隱式內(nèi)存泄漏,而且它的危害性非常大。對于一次性內(nèi)存泄漏,不會進行堆積,相對而言,影響有限。

按泄漏位置分類

根據(jù)內(nèi)存泄漏在內(nèi)存中的位置分為以下兩類:

  • 堆內(nèi)存泄漏:我們經(jīng)常說的內(nèi)存泄漏就是堆內(nèi)存泄漏,在堆上申請了資源,在使用完畢時,沒有將內(nèi)存釋放歸還給OS,從而導致該塊內(nèi)存無法被再次使用。
  • 資源泄漏:通常指的是系統(tǒng)資源,比如socket,文件描述符等,這些資源在系統(tǒng)中都是有限制的,如果創(chuàng)建了而不歸還,久而久之,就會耗盡資源,導致其他程序不可用。

內(nèi)存泄漏的場景

以下以Java語言中的場景來進行說明。

1、被長生命周期對象持有

場景一:在Java中像HashMap、LinkedList等集合類,如果在使用時將其生命為靜態(tài)變量,那么它們的生命周期將伴隨整個JVM的生命周期。在這種場景下,如果持續(xù)將對象放入該類容器,而未進行相應的移除操作,便會形成一個長生命周期(與JVM一樣)的對象,持有了(大量)短生命周期的對象,從而導致短生命周期的對象所占有的內(nèi)存資源無法釋放,從而造成內(nèi)存泄漏問題。

示例如下:

public class TestStaticSet {

    static List<Object> list = new ArrayList<>();

    public void memoryLeakCase1() {
        Object object = new Object();
        list.add(object);
    }
}

場景二:與上述場景類似的,在使用單例模式時,單例的靜態(tài)對象也具有與JVM相同的生命周期,如果該靜態(tài)類持有了外部對象的引用,也會導致外部對象無法被釋放,從而造成內(nèi)存泄漏。

場景三:同樣是一個對象被長期持有,與上面兩種情況不同的是,該對象是某個其他對象的內(nèi)部類。這樣,不僅被長期持有的內(nèi)部類對象無法被釋放,就連內(nèi)部類所在的外部類對象,即便已經(jīng)不再使用,也同樣無法被釋放。

場景四:變量的作用域不同導致的生命周期不同。比如,原本一個變量的作用域在方法內(nèi)部,但如果將該變量設置為類級別的成員變量,此時,原本在方法內(nèi)部使用完即可釋放的內(nèi)存,變?yōu)榕c類對象生命周期一樣長。可能會造成一定程度的內(nèi)存泄漏。

場景五:緩存泄漏。這個場景屬于場景一的拓展場景。比如將對象放入緩存(靜態(tài)集合也可以看做是緩存的容器)中,而忽略了緩存不同場景下的大小以及釋放機制,從而導致一定程度的內(nèi)存泄漏。

以上情況,都可以歸類為由于長生命周期的對象持有了短生命周期的對象,而沒有做好釋放操作而導致內(nèi)存泄漏情況的發(fā)生。

2、系統(tǒng)資源型內(nèi)存泄漏

在項目實踐中會涉及到各類連接性資源,比如數(shù)據(jù)庫連接、網(wǎng)絡連接、流和IO連接等。無論什么時候當我們創(chuàng)建一個連接或打開一個流,JVM都會分配內(nèi)存給這些資源。比如,數(shù)據(jù)庫鏈接、輸入流和session對象。

忘記關閉這些資源,會阻塞內(nèi)存,從而導致GC無法進行清理。特別是當程序發(fā)生異常時,沒有在finally中進行資源關閉的情況。

以數(shù)據(jù)庫操作為例,在對數(shù)據(jù)庫進行操作時,創(chuàng)建的數(shù)據(jù)庫連接使用完畢之后,未調(diào)用對應的close方法進行釋放,便會造成兩個維度的內(nèi)存泄漏問題。

以數(shù)據(jù)庫連接為例:

第一個維度,JVM中大量對象無法釋放。在針對于數(shù)據(jù)庫的操作中,像ConnectionStatementResultSet這些對象都需要顯式地關閉,如果不關閉它們,這些對象不會被垃圾回收器回收,繼而造成JVM內(nèi)部內(nèi)存的占用不斷增加。這會導致Java應用程序內(nèi)存的不斷消耗,最終可能會導致內(nèi)存溢出(OutOfMemoryError)。

第二個維度,數(shù)據(jù)庫連接資源無法釋放。數(shù)據(jù)庫連接是一種寶貴的資源。建立和關閉數(shù)據(jù)庫連接的開銷很高,通常使用連接池來重復利用這些連接。如果數(shù)據(jù)庫連接沒有被顯式關閉,就會被占用在連接池外部。這會導致連接池中的可用連接數(shù)量減少,最終可能用盡連接池,導致后續(xù)請求無法獲取到可用的數(shù)據(jù)庫連接,系統(tǒng)的數(shù)據(jù)庫操作因此陷入僵局。

類似這種場景的資源型泄漏還有HTTP連接、操作本地磁盤文件等場景下的資源釋放。特別是針對異常情況下的資源釋放,否則會引發(fā)偶發(fā)性或隱式內(nèi)存泄漏。

3、監(jiān)聽器和回調(diào)

內(nèi)存泄漏的常見來源還有監(jiān)聽器和其他回調(diào),如果客戶端在對應的API中注冊了回調(diào),卻沒有顯示的取消,那么就會造成積聚,從而引發(fā)內(nèi)存泄漏。這種內(nèi)存泄漏屬于被動型的,類似的要處理好服務端的連接超時、資源超時釋放等場景。

針對上述回調(diào)場景,需要確保回調(diào)立即被當作垃圾回收的最佳方法是只保存它的弱引用,例如將它們保存成為WeakHashMap中的鍵。

4、不當?shù)膃quals方法和hashCode方法實現(xiàn)

當我們定義個新的類時,往往需要重寫equals方法和hashCode方法。在HashSet和HashMap中的很多操作都用到了這兩個方法。如果重寫不得當,會造成內(nèi)存泄漏的問題。

下面來看一個具體的實例:

public class Person { 
    public String name; 
     
    public Person(String name) { 
        this.name = name; 
    } 
}

現(xiàn)在將重復的Person對象插入到Map當中。我們知道Map的key是不能重復的。

@Test 
public void givenMap_whenEqualsAndHashCodeNotOverridden_thenMemoryLeak() { 
    Map<Person, Integer> map = new HashMap<>(); 
    for(int i=0; i<100; i++) { 
        map.put(new Person("jon"), 1); 
    } 
    Assert.assertFalse(map.size() == 1); 
}

上述代碼中將Person對象作為key,存入Map當中。理論上當重復的key存入Map時,會進行對象的覆蓋,不會導致內(nèi)存的增長。

但由于上述代碼的Person類并沒有重寫equals方法,因此在執(zhí)行put操作時,Map會認為每次創(chuàng)建的對象都是新的對象,從而導致內(nèi)存不斷的增長。

VisualVM中顯示信息如下圖:

imgimg

內(nèi)存走勢圖

當重寫equals方法和hashCode方法之后,Map當中便只會存儲一個對象了,內(nèi)存泄漏問題也便解決了。

5、使用ThreadLocal場景

ThreadLocal提供了線程本地變量,它可以保證訪問到的變量屬于當前線程,每個線程都保存有一個變量副本,每個線程的變量都不同。ThreadLocal相當于提供了一種線程隔離,將變量與線程相綁定,從而實現(xiàn)線程安全的特性。

堆棧結構堆棧結構

ThreadLocal的實現(xiàn)中,每個Thread維護一個ThreadLocalMap映射表,key是ThreadLocal實例本身,value是真正需要存儲的Object。

ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個ThreadLocal沒有外部強引用來引用它,那么系統(tǒng)GC時,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現(xiàn)key為null的Entry,就沒有辦法訪問這些key為null的Entry的value。

如果當前線程遲遲不結束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠無法回收,造成內(nèi)存泄漏。

如何解決此問題?

第一,使用ThreadLocal提供的remove方法,可對當前線程中的value值進行移除;

第二,不要使用ThreadLocal.set(null) 的方式清除value,它實際上并沒有清除值,而是查找與當前線程關聯(lián)的Map并將鍵值對分別設置為當前線程和null。

第三,最好將ThreadLocal視為需要在finally塊中關閉的資源,以確保即使在發(fā)生異常的情況下也始終關閉該資源。

try { 
    threadLocal.set(System.nanoTime()); 
    //... further processing 
} finally { 
    threadLocal.remove(); 
}

內(nèi)存泄漏的檢測與定位

檢測和定位內(nèi)存泄漏的方法和場景很多,針對Java語言中的JVM內(nèi)存泄露的排查,介紹幾種常用的方法:

分析堆轉(zhuǎn)儲(Heap Dump Analysis):通過分析堆轉(zhuǎn)儲文件,可以查看當前JVM堆中所有對象的內(nèi)存占用情況。常用的工具包括VisualVM等。

JConsole和Java Mission Control:這兩個工具是Java自帶的性能分析工具,可以實時監(jiān)控JVM的性能指標,包括堆使用情況、垃圾收集情況等。通過這兩個工具,可以快速定位內(nèi)存泄露的問題。

GC日志分析:垃圾收集器的日志文件中記錄了每次垃圾收集的信息,通過分析這些日志文件,可以找出哪些對象占用了大量內(nèi)存并且無法被回收。

代碼審查:通過仔細審查代碼,特別是關注那些可能導致對象長時間被引用的代碼,可以發(fā)現(xiàn)潛在的內(nèi)存泄露問題。

小結

根據(jù)上述案例及場景的分析,我們可以看到,導致內(nèi)存泄漏的場景非常多,但最終歸結成一句話就是內(nèi)存泄漏本身的定義:程序在申請內(nèi)存后,使用完畢之后,無法釋放對應的內(nèi)存空間。

因此,在具體實踐的過程中,針對本文所述場景以及其他涉及資源、內(nèi)存使用的場景要特別留意一下,做好正常、異常邏輯下各類資源的釋放操作。

當然,如果內(nèi)存泄漏已經(jīng)發(fā)生,在尋找內(nèi)存泄漏的問題點時,除了全面定點排查項目中涉及到資源使用的情況之外,還可以結合具體的編程語言(比如,Java的VisualVM等)的內(nèi)存分析工具進行來定位導致內(nèi)存泄漏的地方。關于各類工具的使用及內(nèi)存分析,本篇文章就不再展開。

責任編輯:武曉燕 來源: 程序新視界
相關推薦

2021-06-30 21:21:01

抽象泄漏框架

2021-06-30 00:20:12

Hangfire.NET平臺

2023-05-12 08:19:12

Netty程序框架

2021-08-12 14:19:14

Slice數(shù)組類型內(nèi)存

2021-06-04 09:56:01

JavaScript 前端switch

2021-02-02 18:39:05

JavaScript

2020-11-10 10:48:10

JavaScript屬性對象

2021-01-29 18:41:16

JavaScript函數(shù)語法

2021-05-18 08:30:42

JavaScript 前端JavaScript時

2021-03-09 14:04:01

JavaScriptCookie數(shù)據(jù)

2024-04-19 14:23:52

SwitchJavaScript開發(fā)

2021-03-05 18:04:15

JavaScript循環(huán)代碼

2024-01-30 13:47:45

2021-09-27 09:18:30

ListIterato接口方法

2021-02-26 20:01:57

SVG濾鏡元素

2021-06-24 09:05:08

JavaScript日期前端

2023-05-08 08:21:15

JavaNIO編程

2023-09-06 14:57:46

JavaScript編程語言

2021-01-26 23:46:32

JavaScript數(shù)據(jù)結構前端

2023-07-30 15:18:54

JavaScript屬性
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 夜夜爽99久久国产综合精品女不卡 | 免费日韩网站 | av在线免费网站 | 国产精品久久777777 | 91精品久久久久久久 | 在线观看视频一区 | 国产精品美女 | 超碰在线97国产 | 国产精品久久久久久久免费大片 | 中文字幕在线精品 | www.99热这里只有精品 | 91九色porny首页最多播放 | 午夜精品一区二区三区在线观看 | 国产有码 | 成人在线国产 | 成人亚洲在线 | 久久国产婷婷国产香蕉 | 久久精品国产一区二区电影 | 视频一区二区中文字幕 | 一区二区三区四区av | 成人伊人 | 久久久精 | 日韩色综合 | 久草在线在线精品观看 | 欧美日韩亚洲系列 | 人人色视频 | 美女在线观看国产 | 国产视频在线一区二区 | jlzzxxxx18hd护士| 天堂网av在线 | 久久久久久久网 | 91综合在线观看 | 亚洲欧美v| 在线播放国产一区二区三区 | 成人性视频免费网站 | 毛片一区二区三区 | 在线观看国产视频 | 中文字幕 在线观看 | 伊人精品久久久久77777 | 亚洲日本乱码在线观看 | 日韩在线播放第一页 |