Android内存分析方向:
本篇主要讲解Java内存分析。
查看日志中是否有频繁的GC。通常通过log,我们可以初步定为大部分内存等问题。
Context 泄漏, 主要为Activity 传递泄漏, context 未使用applciationConext 在单例创建时。
Handler 泄漏 , handler中持有view ,context 等做耗时操作。
Cursor 泄漏 , cursor未关闭
register 未 unregister
Bitmap
adapter 未使用convertView
不良代码等
adb shell dumpsys meminfo com.i2finance.shexpress
Applications Memory Usage (kB):
Uptime: 142597122 Realtime: 236611715
** MEMINFO in pid 25126 [com.i2finance.shexpress] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 61111 61084 0 0 69888 64350 5537
Dalvik Heap 49451 49316 0 0 71737 67348 4389
Dalvik Other 3333 3332 0 0
Stack 960 960 0 0
Cursor 12 12 0 0
Ashmem 130 88 0 0
Gfx dev 23780 23780 0 0
Other dev 4 0 4 0
.so mmap 4373 396 3108 0
.jar mmap 80 0 76 0
.apk mmap 17986 64 17580 0
.ttf mmap 96 0 80 0
.dex mmap 15729 16 14244 0
.oat mmap 2378 0 624 0
.art mmap 1859 1624 8 0
Other mmap 2039 12 1308 0
Unknown 84240 84240 0 0
TOTAL 267561 224924 37032 0 141625 131698 9926
App Summary
Pss(KB)
------
Java Heap: 50948
Native Heap: 61084
Code: 36188
Stack: 960
Graphics: 23780
Private Other: 88996
System: 5605
TOTAL: 267561 TOTAL SWAP (KB): 0
Objects
Views: 429 ViewRootImpl: 2
AppContexts: 2 Activities: 1
Assets: 7 AssetManagers: 3
Local Binders: 37 Proxy Binders: 31
Parcel memory: 26 Parcel count: 65
Death Recipients: 2 OpenSSL Sockets: 6
SQL
MEMORY_USED: 567
PAGECACHE_OVERFLOW: 157 MALLOC_SIZE: 62
DATABASES
pgsz dbsz Lookaside(b) cache Dbname
4 24 45 5/24/6 /data/user/0/com.i2finance.shexpress/databases/pa_data_cache.db
4 28 19 1/16/2 /data/user/0/com.i2finance.shexpress/databases/mpush.db
4 60 37 5/18/6 /data/user/0/com.i2finance.shexpress/databases/fstandard.db
4 60 91 466/22/11 /data/user/0/com.i2finance.shexpress/databases/fstandard.db (2)
4 24 40 5/24/6 /data/user/0/com.i2finance.shexpress/databases/pa_data_cache.db
Asset Allocations
zip:/data/user/0/com.i2finance.shexpress/files/paanydoor_resource_3.5.0.36.jar:/resources.arsc: 67K
meminfo
的信息中各字段都是什么含义, 要理解各字段含义,我们才好进行内存的优化。
首先了解两个概念:
通常我们需要关注「PSS TOTAL」 和 「Private Dirty」 .
Dalvik Heap
dalvik虚拟机分配的内存。PSS Total包含所有Zygote分配使用的内存,共享跨进程加权。PrivateDirty 是应用独占内存大小,包含独自分配的部分和应用进程从Zygote复制时被修改的Zygote分配的内存页。
HeapAlloc 是Dalvik堆和本地堆分配使用的大小,它的值比Pss Total和Private Dirty大,因为进程是从Zygote中复制分裂出来的,包含了进程共享的分配部分。
.so mmap & .dex mmap ... mmap
映射本地或虚拟机代码到使用的内存中。Unknown
无法归类的其他项。主要包括大部分的本地分配。Native Heap native
代码申请的内存, 堆和栈,及静态代码块等。TOTAL
进程总使用的实际内存。Objects
中显示持有对象的个数。这些数据也是分析内存泄漏的重要数据。如activity等。Heap Viewer 能做什么?
AS中点击机器人图标打开Android Device Mointor, 如下: 选中进程进行Heap 分析,点击update heap, 查看右侧的heap标签页
Heap视图显示了堆内存使用的情况,每次垃圾回收都会更新,要查看更新情况, 点击Cause GC即可。 下面的内容显示的是分配的内存,按照类型分类:
Paste_Image.png
「如何检查内存泄漏」
我们需要在执行查看内存是否有泄漏的用例之前和之后执行GC,即手动点击Cause GC,观察allocated大小,查看内存是否在一个稳定的数值,多次操作,只要内存稳定,即没有内存泄漏, 如果不断变大,即表示有内存泄漏。
该工具也可以用来查看是否会发生内存抖动
分析内存泄漏,我们需要生成相关的内存Dump,那么我们如何生成dump文件来进行分析。
目前有两种方式:
会生成一份Hprof文件,但该hprof文件我们无法打开,需要进行转换之后才能用MAT工具打开,可以使用命令
hprof-conv com.i2finance.shexpress.hprof xxx.hprof
转换生成可用的hprof文件。
Android Studio 的Android Monitor ,
选中Memory 标签:
点击Dump Java Heap 即可生成对应的hprof文件,在侧边栏中打开Captures文件,选中文件点击右键,export 出标准的hprof文件。
获取Java 堆内存详细信息,可以分析出内存泄漏的问题。
打开Android Studio 的Android Monitor , 选中Memory 标签, 点击Dump heap,生成hprof文件。
AS会自动打开该文件,见下图,但是该功能有点弱,建议还是转换成mat可识别的hprof,使用mat进行分析。
使用内存检测软件leakCanary
在build.gradle
中增加依赖
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
在Applciation
的oncreate
中增加语句
LeakCanary.install(this);
查看leak详情。 当发生内存泄漏时,会生成leak 报告, 报告中会详细写明具体发现内存泄漏的语句。 其原理,可以自行上网搜索查看一下。
Allocation Tracker
能够追踪内存分配信息, 按照顺序排列,这样我们能够清晰的看出来每一个内存对象是怎么一步一步的分配出来的。
比如内存抖动的可疑点,我们可以通过查看其内存分配轨迹来查看段时间内有多少相同或相似对象被创建,进而找到问题发生的代码。
操作步骤:
如上图,上行app 从后台切换道前台时会调用onResume,可以追踪到最后创建了多个Configuration对象。
上图中,Allcated class 表示创建的类型,第一个Allocated in 表示在哪个类中, 第二个Allocated in 表示在哪个方法中。
查看源代码如下:
public Resources getResources() {
Resources res = super.getResources();
Configuration config = new Configuration();
config.setToDefaults();
try {
res.updateConfiguration(config, res.getDisplayMetrics());
}catch (Exception e){
e.printStackTrace();
}
return res;
}
功能同Allocation Tracker(Andorid Device)
, 但是展示更酷炫,更全面。
打开Android Monitor, 选中Memory 标签 , 点击图标
进行内存tracker, 再次点击结束tracker。As会自动打开tracker文件。
下面我们详细看一下这个面板:
AS给我们提供了多种展示方式
AS 还为我们提供了统计,点击饼状图标
按钮即可。
分为两种展示形式,有柱状图和轮胎图,分配比例可选分配次数和占用内存大小:
下图为Sunburst + by Allocator
一个内存的完整路径
比如上行的首页中trace 的数据, 我们看下我们自己的包:
会发现,最外围有很多PageScrollEvent 对象, 我们去看下源代码:
代码如下, 我们发现自动loop的viewpager 每次滑动都会创建多个PageScorllEvent 对象。这样也就对应上面这幅图了。
private class PageChangeListener implements OnPageChangeListener {
private PageChangeListener() {
}
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (isLoop) {
int count = getAdapter().getCount();
if (position < 1 || position > count - 2) {
return;
}
}
LoopViewPager.this.mEventDispatcher.dispatchEvent(new PageScrollEvent(LoopViewPager.this.getId(), SystemClock.uptimeMillis(), position, positionOffset));
}
十. MAT
MAT工具全称为Memory Analyzer Tool,一款详细分析Java堆内存的工具,该工具非常强大,为了使用该工具,我们需要hprof文件.
HPROF文件存储的是特定时间点,java进程的内存快照。有不同的格式来存储这些数据,总的来说包含了快照被触发时java对象和类在heap中的情况。
由于快照只是一瞬间的事情,所以heap dump中无法包含一个对象在何时、何地(哪个方法中)被分配这样的信息。
「几个关键概念」:
打开dump 文件,通常我们需要关注一下几个重要信息, 内存占用饼图,Actions部分的Histogram, Top Consumers
.
我们打开Top Consumers,会生成一个报告,我们可以Biggets Objects overview, 能够看到主要内存占用者
Paste_Image.png
点击下面的biggest Objects 可以查看具体的地址。 还有Biggest Top Level Dominator Classes , 可以看到主要占用内存的都是些什么东东。
MAT中Histogram的主要作用是查看一个instance的数量,一般用来查看自己创建的类的实例的个数。
可以分不同维度来查看对象的Dominator Tree视图,Group by class、Group by class loader、Group by package 和Histogram类似,时间久了,通过多次对比也可以把溢出对象找出来。Histogram 中可以分Group,Thread 区分信息。
通常为:选中某一项-> show objects and class -> by incoming reference->merge shortest path to gc root -> exclude weadk reference
等流程来查看具体情况。
可以在上面过滤相关包名,查看到具体类型, 关注objects个数, 表示内存dump 中有多少个相关类型对象, 比如不改存在的 对象存在了,或者有的对象内存中有太多的份数, 这样就可以进行一个全面分析。
也可以选择Group by package ,这样方便根据package来进行分析。
Paste_Image.png
也可以选择thread来进行分析, 这样查看占用内存最多的线程,这些线程可能为有内存问题的线程。
点击右键常用的几个选项:
图片一直是内存占用的一个大头,也是引起内存泄露,OOM的常客。所以对图片的分析是需要非常了解,这样才能更好的优化项目。
「注意:图片在内存中占用的大小:ARGB_8888 类型的图片 为 内存中图片宽度*内存中图片高度*4, 此处需要注意原始图片宽高和内存图片宽高不一致,包括拉伸和压缩,尤其是图片位置放错,比如1080p设备,xxxhdpi下面没有图片,会去别的目录下寻找图片,此时将会对图片拉伸。」
下面我们来看一下图片的处理。通常dump信息中图片表现为两种类型,Bitmap, byte[]。我们需要知道该图片是哪张图片,这样才能好优化相关的图片代码。
Paste_Image.png
选中mBuffer-> 右键选中Copy-> 选择Save Value To File -> 生成一个xxx.data 文件。
Paste_Image.png
下一步是选中对应的bitmap,打开Inspector 窗口,查看bitmap的尺寸,并且使用GIMP工具(可以安装一个,开源的)打开刚才的data文件,图像类型选择RGB Alpha, 宽度和高度填入图像的宽高,打开即可。
2.3 堆对比
通常为了分析内存是否泄露,内存是否持续增长但没有释放等问题,我们需要dump两次来进行内存堆的对比。
打开两个或多个dump文件,打开Navigation History视图,点击Historgam,选择Add to Comp are Basket,最后选中Compare the Result 。
在对比结果中,主要分析类型或者对象的数量是否有变化, 内存是否有变化。
通过以上手段,我们可以定位到大部分内存问题。
-- END --