記一次項目內存優化--內存泄漏

需求–內存泄漏優化,PSS有所下降, OOM率減少

主要是與某個版本作基準進行對比(一般是最新版本的前一個版本作原數據),優化后,PSS有所下降,線上OOM率減少(Bugly版本對比),泄漏點減少(從捉取一些線上上傳回來的內存堆棧信息分析,或本地測試后dump下hprof文件分析)。

內存泄漏優化的思路

  • 了解什么是內存泄漏
  • 了解虛擬機中的對象的創建過程
  • 了解Java內存分配模型
  • 了解垃圾回收分代收集理論
  • 了解java的引用類型
  • GC是如何判斷對象存活
  • 有哪些對象可作為GC Roots
  • 了解內存泄漏的工具
  • 總結

什么是內存泄漏

App程序中己動態分配的堆內存,由于某種原因,App程序未釋放或無法釋放,會造成系統(手機)內存的浪費。長生命周期對象持有短生命周期對象強引用,從而導致短生命周期對象無法被回收。 我們注意這兩個關鍵詞堆內存、強引用

虛擬機中的對象的創建過程(類的生命周期)

什么都不用說,先上張自畫圖。為大家推薦一本書《深入理解JVM》

第一步,當虛擬機遇到一條new指令時,首先檢查這條指令的參數是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已被加載、解析和初始化過。如果沒有,那必須先執行相應的類加載過程。

第二步,如果檢查通過后,虛擬機將為這個new出的對象進行分配內存。劃分內存是通過指針碰撞、空間列表的組合,同時也考慮并發安全問題(CAS(Compare And Swap的縮寫–樂觀鎖)失敗重試、本地線程內存緩沖)。這中是進行內存分配哦,這時候還不能確定對象所需要的內存大小。在類加載完成后才確定內存的大小。

第三步,內存分配完成后,虛擬機將分配到的內存空間進行初始化為零值(默認的初始值),但不包括對象頭信息,如果使用TLAB(Thread Local Allocation Buffer ,即線程本地分配緩沖區),這過程可以提前至TLAB分配時進行(Eden區劃分出一小塊區域作為TLAB)。這一步操作是保證了對象的實例成員(字段)在Java代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數據類型所對應的零值。

第四步,對象進行必要的設置(主要是一些對象頭信息的設置),例如這個對象的運行狀態、GC分代年齡、鎖狀態、屬于哪個類的實例、哈希碼等等信息。

第五步,從前面幾個步驟知道,只是設置了對象頭信息和所有類成員(字段)賦為默認的初始值,對象并沒有執行方法,所以最后是會接著執行方法,這樣才算創建出一個真正可用的對象。幾乎所有對象是存放在堆區。

Java內存分配模型

使用一張圖,快速了解一下內存分配模型。

方法區

編譯時就分配好,在程序整個運行期間都存在。它用于存儲已經被虛擬機加載的類信息、靜態變量、常量等數據。

堆區

幾乎所有 new 出來的對象是存放在堆區,由 Java 垃圾回收器回收。堆中的對象是垃圾回收的重點。 堆區的劃分新生代、老年代:

新生代

新生代是用來存放新new出來的對象,劃分為 Eden區、From Survivor區 、To Survivor區 。幾乎所以的new出來的對象都會存放在Eden區 (如果new出來的對象占用的內存非常大,在新生代中存放不下,直接進入老年代存放)。

當Eden區的內存空間不足時,系統會觸發Minor GC /Young GC進行回收Eden區的對象(From Survivor區 、To Survivor區不會觸發GC),經過GC后,一些對象仍然存活(對象被引用著–通過GC Root可達性來判斷),則會被移到To Survivor區存放,當對象在Survivor區熬過一次GC后,此對象的GC年齡就會+1(GC年齡是對象頭信息的一個標記參數),會被復制到From Survivor區,當From Survivor區的對象達到一定年齡時(默認年齡是15,但可以通過XX:MaxTenuringThreshold設置),被移到老年代,否則復制到To Survivor區。

老年代

老年代是新生代存放不下的大對象,或對象經過多次Minor GC /Young GC后仍然存活的對象(長期存活的對象)。

當隨著Eden區的Minor GC /Young GC持續進行,老年代的對象持續增加,會導致老年代可用的內存空間也會持續減少,最終系統會觸發Major GC。

元空間(永久代)

永久代(持久代)是存放包含應用的類/方法信息,以及JRE庫的類和方法信息。然而在Java8中,元空間取代了永久代,元空間(Metaspace)被稱為“元數據區”。

需要注意的是:元空間并不在虛擬機中哦,而是使用本地內存(以前永久代是在jvm中的)。這樣就解決了以前永久代的OOM問題,元數據和class對象存放在永久代中,容易出現性能問題和內存溢出,畢竟是和老年代共享堆空間。

堆內存分配策略

內存分配原則

  • 對象優先在Eden分配----如果說Eden內存空間不足,就會發生Minor GC /Young GC。
  • 大對象直接進入老年代----大對象:需要大量連續內存空間的Java對象,比如很長的字符串和大型數組。會導致新生代內存有空間,還是需要提前進行垃圾回收獲取連續空間來放此大對象。Survivor區會進行大量的內存復制,-XX:PretenureSizeThreshold 參數 ,大于這個數量直接在老年代分配,缺省為0 ,表示絕不會直接分配在老年代。當Eden分配和Survivor區都沒有足夠空間存放此大對象時,則直接分配到老年代。
  • 長期存活的對象將進入老年代----Survivor區的對象達到一定年齡時,直接移到老年代。默認15歲,可以通過XX:MaxTenuringThreshold設置。
  • 動態對象年齡判定----為了能更好地適應不同程序的內存狀況,虛擬機并不是永遠地要求對象的年齡必須達到了MaxTenuringThreshold才能晉升老年代,如果在Survivor區中相同年齡所有對象大小的總和大于Survivor區的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。
  • 空間分配擔保:新生代中有大量的對象存活,Survivor區不夠,當出現大量對象在Minor GC后仍然存活的情況(最極端的情況就是內存回收后新生代中所有對象都存活),就需要老年代進行分配擔保,把Survivor區無法容納的對象直接進入老年代,只要老年代的連續空間大于新生代對象的總大小或者歷次晉升的平均大小,就進行Minor GC,否則Full GC。

棧中分配對象

  • 逃逸分析----如果符合逃逸分析規則,則在棧中分配對象。

堆中的優化技術

  • TLAB ----Thread Local Allocation Buffer ,即線程本地線程分配緩沖。

棧區

當方法執行時,會在棧區內存中創建方法體內部的局部變量,方法結束后自動釋放內存。

垃圾回收分代收集理論

我們都知道,在java中不同的對象存在不同的生命周期的,java對象在JVM中也存放在不同的區域,所以對不同生命周期不同的存放區,采取不同的回收策略,以提高效率。

當Eden區的內存空間不足時,系統觸發Minor GC/Young GC, 隨著GC持續進行,老年代的對象持續增加,導致老年代的內存空間不足,系統觸發Major GC。當堆區或方法區內存空間不足時,系統觸發Full GC。

Full GC:清理成本高,系統資源消耗高,對系統性能產生影響,很多性能什么都是針對Full GC進行的。 觸發Full GC的條件有:

  • 調用System.gc()
  • 方法區空間不足
  • 堆區空間不足

不同階段GC的特點

  • Minor GC/Young GC – 執行非常頻繁,速度特別快。
  • Major GC – 速度上,一般會比Minor GC/Young GC慢十倍以上。
  • Full GC – Minor GC和Major GC都會執行,會發出"Stop the World"事件,會中斷程序運行,直到GC完成。所以Full GC時,我們會感知到APP有卡頓之感。

垃圾回收分代收集對應的回收算法

  • 復制算法: 實現簡單,運行高效,內存復制,內存利用率只有一半。
  • 標記-清除: 利用率100%,不需要內復制,有內存碎片
  • 標記-整理:利用率100%,沒有內存碎片,需要內存復制(整理存活的對象,將其拷貝到一塊連續內存中)

GC是如何判斷對象存活

  • 可達性分析 (java) 通過一系列稱之為“GC Roots”的對象作為起始點,從這些節點向下搜索,搜索所有的引用鏈,當一個對象到GC Roots有引用鏈,則說明這個對象存活著;當一個對象到GC Roots沒有任何引用鏈(即GC Roots到對象不可達)時,則證明此對象是不可用的(所謂的垃圾)。

  • 引用計數算法(JVM早期使用的—已經不使用) A對象引用B 對象(+1),同時C對象引用B對象(1+1=2),計數法就是引用一次累加1次,如果沒有引用就累減1次,如果歸到0時,說明沒有引用。缺點:就是相互引用。如A對象引用B對象,同時B對象引用A對象,很難去判斷對象是否應該回收。

在Java, 可作為GC Roots的對象包括:

  • 方法區: 類靜態屬性的對象;
  • 方法區: 常量的對象;
  • 虛擬機棧(本地變量表)中的對象。
  • 本地方法棧JNI(Native方法)中的對象。

四種引用類型

  1. 強引用(StrongReference):JVM 寧可拋出 OOM ,也不會讓 GC 回收具有強引用的對象。
  2. 軟引用(SoftReference):只有在內存空間不足時,對象才會被回收。
  3. 弱引用(WeakReference):在 GC 時,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,對象都會被回收。
  4. 虛引用(PhantomReference):任何時候都可以被GC回收,當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是否存在該對象的虛引用,來了解這個對象是否將要被回收。可以用來作為GC回收Object的標志。

我們定義對象,應該考慮使用那種引用,多考慮使用軟引用(定義一些還有用但并非必須的對象)或弱引用(定義非必須對象)。

Android Studio的profiler工具

  1. 我們也可以利用Android Studio的profiler工具,方便快速查找、觀察,簡單分析一些對象生成情況。
  2. 也可以dump下hprof文件,結合MAT深入分析與排查,對象發生是否泄漏。
  3. 注意:MAT打開Android Studio的profiler里dump下hprof文件時,利用AS自帶的hprof工具轉換一下格式(通過命令hprof-conv -z 原hprof文件 輸出hprof文件),不然打開是亂碼。

當然檢測內存泄漏的工具和方法有很多,就不一一列舉了,感興趣的可以網上查閱一下。

常見的內存問題場景

  • 靜態成員/單例
    • 作為GC ROOT,持有短生命周期引用(如Activity)導致其短生命周期對象無法釋放。
  • 集合類
    • 當使用集合時,只有添加元素,沒有對應的刪除元素。
  • 非靜態內部類/匿名內部類
    • 如Handler postDelayed一個匿名Runnable,退出Activity時消息沒處理完。
  • 上下文 – Context
    • 持有的上下文,需要特別注意。
  • 注冊/反注冊
    • 如EventBus只有注冊沒有注銷。addXXXListener函數,需要有對應的removeXXXListener等等。
  • 未關閉/釋放資源
    • 如FileOutputStream未close。
  • 系統Bug
    • WebView、InputMethodManager等

總結

  • 上面的內存相關知識也是自己學習的一種總結,有錯誤的可以留言指正。
  • 內存優化,需要對下面的知識有一定的了解。
    • Java內存分配模型
    • Java的四大引用及其使用場景
    • 內存檢測工具及常用命令
    • GC Root的定義

為了幫助到大家更好的全面清晰的掌握好性能優化,準備了相關的核心筆記(還該底層邏輯):https://qr18.cn/FVlo89

性能優化核心筆記:https://qr18.cn/FVlo89

啟動優化

內存優化

UI優化

網絡優化

Bitmap優化與圖片壓縮優化https://qr18.cn/FVlo89

多線程并發優化與數據傳輸效率優化

體積包優化

《Android 性能監控框架》:https://qr18.cn/FVlo89

《Android Framework學習手冊》:https://qr18.cn/AQpN4J

  1. 開機Init 進程
  2. 開機啟動 Zygote 進程
  3. 開機啟動 SystemServer 進程
  4. Binder 驅動
  5. AMS 的啟動過程
  6. PMS 的啟動過程
  7. Launcher 的啟動過程
  8. Android 四大組件
  9. Android 系統服務 - Input 事件的分發過程
  10. Android 底層渲染 - 屏幕刷新機制源碼分析
  11. Android 源碼分析實戰

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:
http://www.pswp.cn/news/42214.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/42214.shtml
英文地址,請注明出處:http://en.pswp.cn/news/42214.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

程序員如何利用公網遠程訪問查詢本地硬盤【內網穿透】

🎬 鴿芷咕:個人主頁 🔥 個人專欄: 《高效編程技巧》《cpolar》 ??生活的理想,就是為了理想的生活! 公網遠程訪問本地硬盤文件【內網穿透】 文章目錄 公網遠程訪問本地硬盤文件【內網穿透】前言1. 下載cpolar和Everything軟件1.…

React 生態應用 - React Router(1)

目錄 擴展學習資料 安裝和導入 Route匹配 src/components/navbar.jsx src/App.js 擴展學習資料 資料名稱 鏈接 備注 閱讀react router組件文檔 https://react-router.docschina.org/web/guides/philosophy Introduction | React Router 中文文檔 擴展閱讀 路由鑒權 …

(stm32)低功耗模式

低功耗模式 執行哪個低功耗模式的程序判斷流程 標志位設置操作一定要在WFI/WFE之前,調用此指令后立即進入睡眠判斷流程 模式對比 睡眠模式 停止模式 待機模式

FLatten Transformer

FLatten Transformer: Vision Transformer using Focused Linear Attention ICCV 2023 聚焦式線性注意力模塊 關于Transformer 在Transformer模型應用于視覺領域的過程中,降低自注意力的計算復雜度是一個重要的研究方向。線性注意力通過兩個獨立的映射函數來近似S…

3 Python的數據類型

概述 在上一節,我們介紹了Python的基礎語法,包括:編碼格式、標識符、關鍵字、注釋、多行、空行、縮進、引號、輸入輸出、import、運算符、條件控制、循環等內容。Python是一種動態類型的編程語言,這意味著當你創建一個變量時&…

1.初識Web

文章目錄 1. 什么是Web?2.初始Web前端2.1.Web標準 1. 什么是Web? web:全球廣域網,也稱萬維網(www World Wide Web),能夠通過瀏覽器訪問的網站。 2.初始Web前端 網頁有哪些部分組成? 文字、圖片、音頻、視頻、超鏈接… 我們看到的網頁&am…

react 生命周期方法

組件的生命周期 每個組件都包含 “生命周期方法”,你可以重寫這些方法,以便于在運行過程中特定的階段執行這些方法。你可以使用此生命周期圖譜作為速查表。在下述列表中,常用的生命周期方法會被加粗。其余生命周期函數的使用則相對罕見。 掛…

Windows Oracle21C與PLSQL Developer 15配置

1、下載Oracle21c并安裝 下載地址:https://www.oracle.com/database/technologies/oracle21c-windows-downloads.html 2、下載PLSQL Developer 15并安裝 下載地址:https://www.allroundautomations.com/products/pl-sql-developer/#pricing 3、配置O…

TypeScript教程(四)基本運算符

一、運算符 TypeScript包含以下幾種運算符: 1.算術運算符 2.邏輯運算符 3.關系運算符 4.按位運算符 5.賦值運算符 6.三元/條件運算符 7.字符串運算符 8.類型運算符 1.算術運算符 y5 運算符描述例子x 運算結果y 運算結果加法xy275-減法xy-235*乘法xy*2105…

在線課堂錄播直播管理系統SpringBoot+Vue

在線課堂錄播直播管理系統SpringBootVue 文章目錄 在線課堂錄播直播管理系統SpringBootVue共三個端:后端、后臺管理系統、前端,如要學習看評論區(全部源碼、文檔、數據庫)。內置功能一、前端二、后臺管理三、后端--代碼全有。四、…

數據結構—排序

8.排序 8.1排序的概念 什么是排序? 排序:將一組雜亂無章的數據按一定規律順序排列起來。即,將無序序列排成一個有序序列(由小到大或由大到小)的運算。 如果參加排序的數據結點包含多個數據域,那么排序往…

ElasticSearch刪除索引【真實案例】

文章目錄 背景分析解決遇到的問題 - 刪除超時報錯信息解決辦法1:調大超時時間解決辦法2:調大ES堆內存參考背景 項目中使用了ELK技術棧實現了日志管理,但是日志管理功能目前并沒有在生產上實際使用。 但ELK程序依然在運行,導致系統磁盤發生告警,剩余可用磁盤不足10%。 所以…

async和await

一,基本使用 其實就是之前學過的異步函數,異步編程在函數前寫一個ansyc,就轉化為異步函數,返回的是一個promise對象,于是就可以使用await關鍵字,可以把異步函數寫成同步函數的形式,極大地提高代…

LVS-DR的RS進行ARP抑制的原因和LVS持久連接配置

一.RS的ARP抑制 1.為什么要抑制 2.如何抑制 (1)修改/etc/sysctl.conf文件,增加以下內容 (2)命令行臨時設置 二.LVS持久連接 1.客戶端持久連接 2.端口持久連接 3.防火墻標記持久連接 一.RS的ARP抑制 1.為什么要…

Binary operator ‘*‘ cannot be applied to two ‘Double?‘ operands

在 swift 中聲明 Double 類型參數變量在進行運算處理時拋出了如下異常 Binary operator * cannot be applied to two Double? operands 情況一 參數類型不匹配,需將參數類型進行匹配 self.max height / (length * width) // 初始 self.max height / (length * Double(wid…

Java“牽手”根據關鍵詞搜索(分類搜索)京東商品列表頁面數據獲取方法,京東API實現批量商品數據抓取示例

京東商城是一個網上購物平臺,售賣各類商品,包括服裝、鞋類、家居用品、美妝產品、電子產品等。要獲取京東商品列表和商品詳情頁面數據,您可以通過開放平臺的接口或者直接訪問京東商城的網頁來獲取商品詳情信息。以下是兩種常用方法的介紹&…

學校信息管理系統說明文檔

目錄 0學生信息管理系統體驗教程. 4 0.0Student management異地打開方法:. 4 1. 管理系統設計需求分析. 6 1.1 需求介紹. 6 1.2功能需求. 6 1.2.1 學生信息錄入. 6 1.2.2 學生信息查詢. 6 1.2.3 權限管理. 6 1.2.4 添加學生信息驗證. 6 2.功能介紹. 7 2.1…

快速上手PyCharm指南

PyCharm簡介 PyCharm是一種Python IDE(Integrated Development Environment,集成開發環境),帶有一整套可以幫助用戶在使用Python語言開發時提高其效率的工具,比如調試、語法高亮、項目管理、代碼跳轉、智能提示、自動…

idea如何建立web項目???

我們需要用到tomcat,沒有下在著小伙伴,可以借鑒這篇博客: 如何正確下載tomcat???_明天更新的博客-CSDN博客 1.建立普通的Java項目。 2.簡單編寫index.jsp文件 3.添加tomcat 4.運行服務器 5.構建Servlet 最后…

嵌入式編譯FFmpeg6.0版本并且組合x264

下載直通車:我用的是6.0版本的 1.準備編譯: 2.進入ffmpeg源碼目錄,修改Makefile,添加編譯選項: CFLAGS -fPIC 不加會報錯 3.使用命令直接編譯 ./configure --cross-prefix/home/xxx/bin/arm-linux-gnueabihf- --enable-cross-compile --targ…