android 內存泄露-抓出重要函數-GL_OUT_OF_MEMORY-GL error: Out of memory!OpenGLRenderer

一般log有錯誤的內存泄露提示“GL error:  Out of memory!”"GL_OUT_OF_MEMORY",我們就需要使用工具去一步一步的獲取哪些模塊類裏面的方法出了問題,然後一個一個去嘗試找出問題,以下是個人經歷:

問題點:藍牙傳輸多個文件,引發藍牙報停,log打印crash:

OpenGLRenderer: GL error:  Out of memory!                                                                                                            
OpenGLRenderer: glFinish error! GL_OUT_OF_MEMORY (0x505)                                                                                             
PackageManager: getApplicationInfo com.android.bluetooth: Package{8e1b6a4 com.android.bluetooth}                                                     
PackageManager: getApplicationInfo com.android.bluetooth: Package{8e1b6a4 com.android.bluetooth}                                                     
                                                                                                                                                     
libc    : Fatal signal 6 (SIGABRT), code -6 in tid 3504 (RenderThread), pid 3235 (droid.bluetooth)                                                   
PackageManager: getApplicationInfo com.android.bluetooth: Package{8e1b6a4 com.android.bluetooth}                                                     
PackageManager: getApplicationInfo com.android.bluetooth: Package{8e1b6a4 com.android.bluetooth}                                                     
PackageManager: getApplicationInfo com.android.bluetooth: Package{8e1b6a4 com.android.bluetooth}                                                     
PackageManager: getApplicationInfo com.android.bluetooth: Package{8e1b6a4 com.android.bluetooth}                                                     
PackageManager: getApplicationInfo com.android.bluetooth: Package{8e1b6a4 com.android.bluetooth}                                                     
PackageManager: getApplicationInfo com.android.bluetooth: Package{8e1b6a4 com.android.bluetooth}                                                     
crash_dump32: obtaining output fd from tombstoned, type: kDebuggerdTombstone                                                                         
/system/bin/tombstoned: received crash request for pid 3235                                                                                          
crash_dump32: performing dump of process 3235 (target tid = 3504)                                                                                    
DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***                                                                            
DEBUG   : Build fingerprint: 'Itel/F0901/itel-A22:8.1.0/0209/A22-F0901-8.1.0-OP-V001-20180209:user/release-keys'                                     
DEBUG   : Revision: '0'                                                                                                                              
DEBUG   : ABI: 'arm'                                                                                                                                 
DEBUG   : pid: 3235, tid: 3504, name: RenderThread  >>> com.android.bluetooth <<<                                                                    
DEBUG   : signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------                                                                                
DEBUG   : Abort message: 'glFinish error! GL_OUT_OF_MEMORY (0x505)'                                                                                  
DEBUG   :     r0 00000000  r1 00000db0  r2 00000006  r3 00000008                                                                                     
DEBUG   :     r4 00000ca3  r5 00000db0  r6 918f53ac  r7 0000010c                                                                                     
DEBUG   :     r8 00000000  r9 00000505  sl ac77bee0  fp ac7e4c37                                                                                     
DEBUG   :     ip ac77beb4  sp 918f5398  lr ad87ade3  pc ad8747cc  cpsr 20070030                                                                      
DEBUG   :                                                                                                                                            
DEBUG   : backtrace:                                                                                                                                 
DEBUG   :     #00 pc 0001a7cc  /system/lib/libc.so (abort+63)                                                                                        
DEBUG   :     #01 pc 000065c3  /system/lib/liblog.so (__android_log_assert+154)                                                                      
DEBUG   :     #02 pc 0002feb1  /system/lib/libhwui.so (android::uirenderer::debug::GlesErrorCheckWrapper::assertNoErrors(char const*)+200)           
DEBUG   :     #03 pc 0005c935  /system/lib/libhwui.so (android::uirenderer::Caches::flush(android::uirenderer::Caches::FlushMode)+116)               
DEBUG   :     #04 pc 000490db  /system/lib/libhwui.so (android::uirenderer::renderthread::CanvasContext::destroy()+38)                               
DEBUG   :     #05 pc 00048f71  /system/lib/libhwui.so (android::uirenderer::renderthread::CanvasContext::~CanvasContext()+28)                        
DEBUG   :     #06 pc 00049171  /system/lib/libhwui.so (android::uirenderer::renderthread::CanvasContext::~CanvasContext()+2)                         
DEBUG   :     #07 pc 0004f701  /system/lib/libhwui.so (android::uirenderer::renderthread::Bridge_destroyContext(android::uirenderer::renderthread::de
DEBUG   :     #08 pc 000508cb  /system/lib/libhwui.so (android::uirenderer::renderthread::MethodInvokeRenderTask::run()+10)                          
DEBUG   :     #09 pc 00050a53  /system/lib/libhwui.so (android::uirenderer::renderthread::SignalingRenderTask::run()+10)                             
DEBUG   :     #10 pc 000517af  /system/lib/libhwui.so (android::uirenderer::renderthread::RenderThread::threadLoop()+178)                            
DEBUG   :     #11 pc 0000d199  /system/lib/libutils.so (android::Thread::_threadLoop(void*)+144)                                                     
DEBUG   :     #12 pc 0006e42d  /system/lib/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+80)                                
DEBUG   :     #13 pc 00048043  /system/lib/libc.so (__pthread_start(void*)+22)                                                                       
DEBUG   :     #14 pc 0001b3a7  /system/lib/libc.so (__start_thread+32)  
                                                                            

針對這類log,我們都很無助,網上有很多專門lib解析分析函數的報錯,我以前試過,沒什麼弄的,就算找到lib庫的報錯函數你也不敢去修改,因爲那多是android正常的庫函數,肯定沒有問題,有問題也不是你應該去擔心和修改的,所以這裏我們就不去探討怎麼解析lib庫追查函數(有這方面的高手請別指責我們,方便的話留個詳細分析方案教下我們),我們直接看錯誤類型

GL error:  Out of memory!

這種錯誤,百度一搜都知道是內存泄露,什麼是常見的內存泄露,有解釋:你不瞭解什麼叫內存泄露,那你根本無法分析這類問題與着手追查,請文章最後轉載小框的文章瞭解:

下面開始我的簡易內存泄露分析:

1:先用工具把內存泄露dump文件*.hprof弄出來

    弄這個文件很簡單,我直接用Android Studio,我們直接看下圖,直觀,怎麼操作怎麼抓取dump文件


2:轉換Android Studio的dump文件,因爲Android Studio抓取生成的*.hprof文件你是沒法用eclipse的MAT工具去解析,所以我們要做個轉換,很簡單,用SDK的工具:看看下面命令就你知道了

E:\工具\tools>hprof-conv.exe AAAA.hprof BBBB-3.hprof

adb進入SDK的tools目錄下,看看有沒有hprof-conv.exe,沒有就自己去下,命令內容是:

hprof-conv.exe+空格+Android Studio的hprof源文件+空格+你要轉換後保存的hprof文件

3:MAT解析android的內存泄露dump問題

下面我們來用工具mat去解析android的內存泄露dump問題,現在網上主流都是用mat解析

以下是來至的文章,我寫文章一般很少,百度能找到就不重複了,只是拷貝別人的,做個備份,以後好找

我在百度網盤直接上傳了MAT工具:https://pan.baidu.com/s/1ylzbmWeLSiVq-mOPaxO1xw

沒有密碼隨便下

下面是我轉了別人用eclipse抓的DUMP和解析,MAT解析的都一樣,你們看他的截圖就明白了,最後就是篩選出有內存泄露的可疑的一些方法,然後自己去做排查

       最近一段時間一直在研究熱部署,熱部署中涉及到一個比較頭痛的問題就是查內存泄露(Memory Leak),於是乎在研究熱部署的過程中,乾的最多的一件事就是查內存泄露。
       查內存泄露,最開始嘗試用JDK自身的工具去解決這件事,通過jstat和jmap,去發現是否有內存泄露,當判斷有內存泄露存在時,試圖要去尋找內存泄露的點時,發現單純使用JDK自身提供的工具沒有什麼很好的辦法,我嘗試過Jhat,發現查起來太困難了,後來對比網上推薦的工具,我選擇了MAT(Memory Analyzer Tool)。
       MAT是一個eclipse的插件,上手起來比較快。它能夠快速的分析dump文件,可以直觀的看到各個對象在內存佔用的量大小,以及類實例的數量,對象之間的引用關係,找出對象的GC Roots相關的信息,此外還能生成內存泄露報表,疑似泄露大對象的報表等等。

  • 安裝MAT
  • 可以選擇eclipse插件的方式安裝
    • http://download.eclipse.org/mat/1.3/update-site/
  • 也可以選擇單獨MAT程序下載安裝
    • http://www.eclipse.org/mat/downloads.php
  • 使用MAT查內存溢出
    • 生成dump
  • 生成dump文件,可以直接用 jmap -dump:format=b,file=xxx.bin ${pid}的方式
  • 也可以直接用MAT生成,File-》Acquire Heap Dump -》選擇要dump的java進程-》finish就可以了
  • 生成完dump後,可以用MAT打開 dump(如果是MAT dump完後會自動進行解析),File-》Open Heap Dump 對dump文件進行解析,最終生成一個Overview視圖,這個圖是一個概要圖,顯示了一些統計信息,包括整個size大小,class數量,以及對象 的數量,同時還將生成一個大對象的top圖,併線顯示大對象佔用內存的百分比。
    • 類似:size:2.2MB Classes:3.3k Objects:50.1k ClassLoader:84 Unreachable Objects Histogram
  • 找出溢出源
    • Histogram視圖(截圖裏柱子那個,邊上的是Dominator Tree ):列出每個class產生了多少個實例,以及佔有多大內存,所佔百分比
      • 可以很容易找出站內存最多的幾個類,根據Retained Heap排序,找出前幾個。
      • 可以分不同的維度來查看類的Histogram視圖,Group by class、Group by superclass、Group by class  loader、Group by package
      • 只要有溢出,時間久了,溢出類的實例數量或者其佔有的內存會越來越多,排名也就越來越前,通過多次對比不同時間點下的Histogram圖對比就能很容易把溢出類找出來。


      •  
      • Dominator Tree(支配樹):列出每個對象(Object instance)與其引用關係的樹狀結構,還包含了佔有多大內存,所佔百分比
  • 可以很容易的找出佔用內存最多的幾個對象,根據Percentage(百分比)來排序。
  • 可以分不同維度來查看對象的Dominator Tree視圖,Group by class、Group by class  loader、Group by package
  • 和Histogram類似,時間久了,通過多次對比也可以把溢出對象找出來,Dominator Tree和Histogram的區別是站的角度不一樣,Histogram是站在類的角度上去看,Dominator Tree是站的對象實例的角度上看,Dominator Tree可以更方便的看出其引用關係。


  •  
  • 定位溢出的原因
    • 通過Path to GC Roots或者Merge Shortest Paths to GC Roots


    •  
  • 通 過Histogram視圖或者Dominator Tree視圖,找到疑似溢出的對象或者類後,選擇Path to GC Roots或者Merge Shortest Paths to GC Roots,這裏有很多過濾選項,一般來講可以選擇exclude all plantom/weak/soft etc. references。這樣就排除了虛引用、弱引用、以及軟引用,剩下的就是強引用。從GC上說,除了強引用外,其他的引用在JVM需要的情況下是都可以 被GC掉的,如果一個對象始終無法被GC,就是因爲強引用的存在,從而導致在GC的過程中一直得不到回收,因此就內存溢出了。
  • 接下來就需要直接定位具體的代碼,看看如何釋放這些不該存在的對象,比如是否被cache住了,還是其他什麼原因。
  • 找到原因,清理乾淨後,再對照之前的操作,看看對象是否還再持續增長,如果不在,那就說明這個溢出點被成功的堵住了。
  • 最後用jstat跟蹤一段時間,看看Old和Perm區的內存是否最終穩定在一個範圍內,如果長時間穩定在一個範圍,那溢出的問題就解決了,如果還再繼續增長,那繼續用上述方法,看看是否存在其他代碼的溢出點,繼續找出,將其堵住。


  •  

     
  • 此外通過list objects或show objects by class也可以達到類似的效果,不過沒看GC Roots的方式直觀,這裏就不細說了。
    • list objects -- with outgoing references : 查看這個對象持有的外部對象引用。
    • list objects -- with incoming references : 查看這個對象被哪些外部對象引用。
    • show objects by class  --  with outgoing references :查看這個對象類型持有的外部對象引用
    • show objects by class  --  with incoming references :查看這個對象類型被哪些外部對象引用  

==========================================================

以下內容轉發來至:請感謝他的共享,寫得非常好。
作者:小筐子
鏈接:https://www.jianshu.com/p/bf159a9c391a
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

內存泄露

說到內存泄露,就不得不提到內存溢出,這兩個比較容易混淆的概念,我們來分析一下。

  • 內存泄露程序在向系統申請分配內存空間後(new),在使用完畢後未釋放。結果導致一直佔據該內存單元,我們和程序都無法再使用該內存單元,直到程序結束,這是內存泄露。

  • 內存溢出程序向系統申請的內存空間超出了系統能給的。比如內存只能分配一個int類型,我卻要塞給他一個long類型,系統就出現oom。又比如一車最多能坐5個人,你卻非要塞下10個,車就擠爆了。大量的內存泄露會導致內存溢出(oom)。

這個時候,我們就應該瞭解下什麼是內存

內存

想要了解內存泄露,對內存的瞭解必不可少。
JAVA是在JVM所虛擬出的內存環境中運行的,JVM的內存可分爲三個區:堆(heap)、棧(stack)和方法區(method)。

  • 棧(stack):是簡單的數據結構,但在計算機中使用廣泛。棧最顯著的特徵是:LIFO(Last In, First Out, 後進先出)。比如我們往箱子裏面放衣服,先放入的在最下方,只有拿出後來放入的才能拿到下方的衣服。棧中只存放基本類型和對象的引用(不是對象)。

  • 堆(heap)堆內存用於存放由new創建的對象和數組。在堆中分配的內存,由java虛擬機自動垃圾回收器來管理。JVM只有一個堆區(heap)被所有線程共享,堆中不存放基本類型和對象引用,只存放對象本身。

  • 方法區(method):又叫靜態區,跟堆一樣,被所有的線程共享。方法區包含所有的class和static變量。

內存泄露原因分析

在JAVA中JVM的棧記錄了方法的調用,每個線程擁有一個棧。在線程的運行過程當中,執行到一個新的方法調用,就在棧中增加一個內存單元,即幀(frame)。在frame中,保存有該方法調用的參數、局部變量和返回地址。然而JAVA中的局部變量只能是基本類型變量(int),或者對象的引用。所以在棧中只存放基本類型變量和對象的引用。引用的對象保存在堆中。

當某方法運行結束時,該方法對應的frame將會從棧中刪除,frame中所有局部變量和參數所佔有的空間也隨之釋放。線程回到原方法繼續執行,當所有的棧都清空的時候,程序也就隨之運行結束。

而對於堆內存,堆存放着普通變量。在JAVA中堆內存不會隨着方法的結束而清空,所以在方法中定義了局部變量,在方法結束後變量依然存活在堆中。

綜上所述,棧(stack)可以自行清除不用的內存空間。但是如果我們不停的創建新對象,堆(heap)的內存空間就會被消耗盡。所以JAVA引入了垃圾回收(garbage collection,簡稱GC)去處理堆內存的回收,但如果對象一直被引用無法被回收,造成內存的浪費,無法再被使用。所以對象無法被GC回收就是造成內存泄露的原因!

垃圾回收機制

垃圾回收(garbage collection,簡稱GC)可以自動清空堆中不再使用的對象。在JAVA中對象是通過引用使用的。如果再沒有引用指向該對象,那麼該對象就無從處理或調用該對象,這樣的對象稱爲不可到達(unreachable)。垃圾回收用於釋放不可到達的對象所佔據的內存。

實現思想:我們將棧定義爲root,遍歷棧中所有的對象的引用,再遍歷一遍堆中的對象。因爲棧中的對象的引用執行完畢就刪除,所以我們就可以通過棧中的對象的引用,查找到堆中沒有被指向的對象,這些對象即爲不可到達對象,對其進行垃圾回收。


垃圾回收實現思想

如果持有對象的強引用,垃圾回收器是無法在內存中回收這個對象。

引用類型

在JDK 1.2以前的版本中,若一個對象不被任何變量引用,那麼程序就無法再使用這個對象。也就是說,只有對象處於可觸及(reachable)狀態,程序才能使用它。從JDK 1.2版本開始,把對象的引用分爲4種級別,從而使程序能更加靈活地控制對象的生命週期。這4種級別由高到低依次爲:強引用、軟引用、弱引用和虛引用。
Java/Android引用類型及其使用分析

1. 強引用(Strong reference)
實際編碼中最常見的一種引用類型。常見形式如:A a = new A();等。強引用本身存儲在棧內存中,其存儲指向對內存中對象的地址。一般情況下,當對內存中的對象不再有任何強引用指向它時,垃圾回收機器開始考慮可能要對此內存進行的垃圾回收。如當進行編碼:a = null,此時,剛剛在堆中分配地址並新建的a對象沒有其他的任何引用,當系統進行垃圾回收時,堆內存將被垃圾回收。

2. 軟引用(Soft Reference)
軟引用的一般使用形式如下:

A a = new A();
SoftReference<A> srA = new SoftReference<A>(a);

軟引用所指示的對象進行垃圾回收需要滿足如下兩個條件:
1.當其指示的對象沒有任何強引用對象指向它;
2.當虛擬機內存不足時。
因此,SoftReference變相的延長了其指示對象佔據堆內存的時間,直到虛擬機內存不足時垃圾回收器纔回收此堆內存空間。

3. 弱引用(Weak Reference)
同樣的,軟引用的一般使用形式如下:

A a = new A();
WeakReference<A> wrA = new WeakReference<A>(a);

WeakReference不改變原有強引用對象的垃圾回收時機,一旦其指示對象沒有任何強引用對象時,此對象即進入正常的垃圾回收流程。

4. 虛引用(Phantom Reference)
與SoftReference或WeakReference相比,PhantomReference主要差別體現在如下幾點:
1.PhantomReference只有一個構造函數

PhantomReference(T referent, ReferenceQueue<? super T> q)

2.不管有無強引用指向PhantomReference的指示對象,PhantomReference的get()方法返回結果都是null。

因此,PhantomReference使用必須結合ReferenceQueue;
與WeakReference相同,PhantomReference並不會改變其指示對象的垃圾回收時機。

內存泄露原因

如果持有對象的強引用,垃圾回收器是無法在內存中回收這個對象。

內存泄露的真因是:持有對象的強引用,且沒有及時釋放,進而造成內存單元一直被佔用,浪費空間,甚至可能造成內存溢出!

其實在Android中會造成內存泄露的情景無外乎兩種:
  • 全局進程(process-global)的static變量。這個無視應用的狀態,持有Activity的強引用的怪物。
  • 活在Activity生命週期之外的線程。沒有清空對Activity的強引用。

檢查一下你的項目中是否有以下幾種情況:

  • Static Activities
  • Static Views
  • Inner Classes
  • Anonymous Classes
  • Handler
  • Threads
  • TimerTask
  • Sensor Manager

詳解見該文章《Android內存泄漏的八種可能》

最後推薦一個可檢測app內存泄露的項目:LeakCanary(可以檢測app的內存泄露)


以上內容轉發來至:請感謝他的共享,寫得非常好。
作者:小筐子
鏈接:https://www.jianshu.com/p/bf159a9c391a
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
==========================================================


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章