记一次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
搜索结果查看默认第一栏,如果没有泄漏,关闭VideoActivity之后,Objects数量一般是零,如果不为零,则可能存在泄漏。
右键Merge Shortest Paths to GC Roots/exclude all phantom/weak/soft etc,references/ 筛选出强引用的对象。
筛选出结果后,出现com.voyah.cockpit.video.ui.VideoActivity$1 @0x3232332 JIN Global 信息,且无法继续跟踪下去。
筛选出结果之后显示有六个VideoActivity对象没有释放,点击该对象也无法看到GC对象路径。(正常的java层内存泄漏能够看到泄漏的对象具体是哪一个)
正常的内存泄漏能够看到具体对象,如图:
这个MegaDataStorageConfig就是存在内存泄漏。
而我们现在的泄漏确实只知道VideoActivity$1 对象泄漏了,没有具体的对象,这样就没有办法跟踪下去了。
解决办法:
虽然无法继续跟踪,但泄漏的位置说明就是这个VideoActivity1这个Class类(class.dex可能有很多,一个个找),打开这个class,查看字节码(可以android studio中快捷打开build中的apk),根据【 .line 406 】等信息定位代码的位置,找到泄漏点。
根据方法名、代码行数、类名,直接定位到了存在泄漏的代码:
红框区内就是内存泄漏的代码,这个回调是一个三方sdk工具,我使用时进行了注册,在onDestory中反注册,但还是存在内存泄漏。(该对象未使用是我代码修改之后的)
修改方法
将这个回调移动到Application中去,然后进行事件或者回调的方式通知VideoActivity,在VideoActivity的onDestory中进行销毁回调。
修改完之后,多次进入VideoAcitivity然后在退出,导出hprof文件到mat中筛选查看,如图:
VideoActiviyty的对象已经变成了零,说明开始存在的内存泄漏已经修改好了,使用android proflier工具也能看到在退出videoactivity界面之后主动进行几次gc回收,内存使用量会回归到进入该界面之前。
后续补充。
上次写的不太详细,这一次重新遇到,所以重新补充流程。
在开发中又出现了一次jni内存泄露。 第一个是全类的字节码,不用看。看mat泄露的文件类IjkVideoView$10,注意后面的序号10,因为还有其他的IjkVideoView序号,并不一定是泄露;它一般是软引用,因为序号10没有释放而导致其无法释放。
IjkVideoView$10 这个字节码里面代码并不多,可以很清楚的定位到泄露的代码。
我们打开这个文件。
搜索泄露的类,IjkVideoView。
注意第二个红框中的.line 1189,这个是泄露代码位置。
可以看到泄露的流程是从上到下,IjkVideoView类中的->createPlayer(I)方法IMediaPlayer对象。(实际代码中是IjkMediaPlayer,继承至IMediaPlayer)。
direct methods 是直接调用的方法,能看到行数:line 1189。
通过行数直接在IjkVideoView.java文件中找到该行数,代码如下。
virtual methods 是虚拟方法,一般是回调方法,在java代码中1189行也能看到这几个回调。
去掉1189代码:ijkMediaPlayer.setAndroidIOCallback() 再进行验证,发现泄露没有发生。
总结:
- LeakCanary工具为辅助,MAT工具进行具体分析。因为LeakCanary工具的监听并不准确,如触发leakcanary泄漏警告时代码已经泄漏了很多次。
- 如果能够直接查看泄漏的对象,那是最好修改的,如果不能直接定位泄漏的对象,可以通过泄漏的Class对象在apk解压中找到改class,查看字节码定位具体的代码泄漏位置。
- 使用第三方的sdk时,最好使用Application Context,统一分发统一管理,减少内存泄漏。