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
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
==========================================================


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