Android记一次JNI内存泄漏

1,393 阅读3分钟

记一次JNI内存泄漏

前景

在视频项目播放界面来回退出时,会触发内存LeakCanary内存泄漏警告。

分析

查看leakCanary的日志没有看到明确的泄漏点,所以直接取出leakCanary保存的hprof文件,保存目录在日志中有提醒,需要注意的是如果是android11系统及以上的保存目录和android11以下不同,android11保存的目录在:

   /data/media/10/Download/leakcanary-包名/2023-03-14_17-19-45_115.hprof 

使用Memory Analyzer Tool(简称MAT) 工具进行分析,需要讲上面的hrof文件转换成mat需要的格式:

   hprof-conv -z 转换的文件 转换后的文件
    
   hprof-conv -z 2023-03-14_17-19-45_115.hprof mat115.hprof

打开MAT,导入mat115文件,等待一段时间。

在预览界面打开Histogram,搜索需要检测的类,如:VideoActivity

screenshot-20230314-204413.png

搜索结果查看默认第一栏,如果没有泄漏,关闭VideoActivity之后,Objects数量一般是零,如果不为零,则可能存在泄漏。

右键Merge Shortest Paths to GC Roots/exclude all phantom/weak/soft etc,references/ 筛选出强引用的对象。

image.png

筛选出结果后,出现com.voyah.cockpit.video.ui.VideoActivity$1 @0x3232332 JIN Global 信息,且无法继续跟踪下去。

screenshot-20230314-205257.png

筛选出结果之后显示有六个VideoActivity对象没有释放,点击该对象也无法看到GC对象路径。(正常的java层内存泄漏能够看到泄漏的对象具体是哪一个)

正常的内存泄漏能够看到具体对象,如图:

image.png 这个MegaDataStorageConfig就是存在内存泄漏。

而我们现在的泄漏确实只知道VideoActivity$1 对象泄漏了,没有具体的对象,这样就没有办法跟踪下去了。

解决办法:

虽然无法继续跟踪,但泄漏的位置说明就是这个VideoActivity1,我们可以解压apk,在包内的class.dex中找到VideoActivity1 ,我们可以解压apk,在包内的class.dex中找到VideoActivity1这个Class类(class.dex可能有很多,一个个找),打开这个class,查看字节码(可以android studio中快捷打开build中的apk),根据【 .line 406 】等信息定位代码的位置,找到泄漏点。

screenshot-20230314-205442.png

screenshot-20230314-205600.png screenshot-20230314-205523.png

根据方法名、代码行数、类名,直接定位到了存在泄漏的代码:

screenshot-20230314-205730.png

红框区内就是内存泄漏的代码,这个回调是一个三方sdk工具,我使用时进行了注册,在onDestory中反注册,但还是存在内存泄漏。(该对象未使用是我代码修改之后的)

修改方法

将这个回调移动到Application中去,然后进行事件或者回调的方式通知VideoActivity,在VideoActivity的onDestory中进行销毁回调。

修改完之后,多次进入VideoAcitivity然后在退出,导出hprof文件到mat中筛选查看,如图:

image.png

VideoActiviyty的对象已经变成了零,说明开始存在的内存泄漏已经修改好了,使用android proflier工具也能看到在退出videoactivity界面之后主动进行几次gc回收,内存使用量会回归到进入该界面之前。

后续补充。

上次写的不太详细,这一次重新遇到,所以重新补充流程。

在开发中又出现了一次jni内存泄露。 第一个是全类的字节码,不用看。看mat泄露的文件类IjkVideoView$10,注意后面的序号10,因为还有其他的IjkVideoView序号,并不一定是泄露;它一般是软引用,因为序号10没有释放而导致其无法释放。

d323ed6b-6803-4675-98b4-2f476efdaf7e.jpeg

f4860ddd-8ebc-4f62-9046-fec9768f8b85.jpeg

IjkVideoView$10 这个字节码里面代码并不多,可以很清楚的定位到泄露的代码。

我们打开这个文件。

搜索泄露的类,IjkVideoView。

0396bf0c-8083-4124-8e81-ac6e7b532e58.jpeg

注意第二个红框中的.line 1189,这个是泄露代码位置。

image.png

可以看到泄露的流程是从上到下,IjkVideoView类中的->createPlayer(I)方法IMediaPlayer对象。(实际代码中是IjkMediaPlayer,继承至IMediaPlayer)。

direct methods 是直接调用的方法,能看到行数:line 1189。

image.png

通过行数直接在IjkVideoView.java文件中找到该行数,代码如下。

image.png

virtual methods 是虚拟方法,一般是回调方法,在java代码中1189行也能看到这几个回调。 image.png

去掉1189代码:ijkMediaPlayer.setAndroidIOCallback() 再进行验证,发现泄露没有发生。

总结:

  1. LeakCanary工具为辅助,MAT工具进行具体分析。因为LeakCanary工具的监听并不准确,如触发leakcanary泄漏警告时代码已经泄漏了很多次。
  2. 如果能够直接查看泄漏的对象,那是最好修改的,如果不能直接定位泄漏的对象,可以通过泄漏的Class对象在apk解压中找到改class,查看字节码定位具体的代码泄漏位置。
  3. 使用第三方的sdk时,最好使用Application Context,统一分发统一管理,减少内存泄漏。