前言: 最近在系统性的温习了一遍android性能优化。写博客是学习也是记录,希望在记录的同时也能帮助其他同学。最近我觉得我想出一个不懂系列。“不懂揍我”,“不懂砍我”,“不懂捶我”
一、Profiler分析内存抖动
在我们开发项目中,如果稍不注意,往往会出现内存抖动的情况。而有些内存抖动也可能造成我们的程序卡顿,甚至泄漏。接下来使用Android Studio自带的Profiler分析内存抖动。
1.1、模拟内存抖动并打开Profiler
首先在MainActivity创建一段内存抖动的代码:
private Handler mHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//创造内存抖动
for (int i = 0; i < 100; i++) {
String arg[] = new String[100000];
}
mHandler.sendEmptyMessageDelayed(0, 30);
}
};
然后点击下图红色框内按钮运行程序
运行之后出现如下图,
鼠标移动到MEMORY上双击,进入内存分析
如果你的项目已经运行,也可以通过View --> Tool Windows --> Profiler打开
1.2、分析内存抖动
点击按钮,触发内存抖动的代码,来看看我们的内存情况,可以看到呈锯齿状
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-It94iFlb-1589018239602)(https://user-gold-cdn.xitu.io/2020/5/9/171f780686912499?w=1613&h=558&f=png&s=124757)]
点击上图红色框内record,然后点击stop分析一段我们的锯齿状,如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FaU9X4Ny-1589018239603)(https://user-gold-cdn.xitu.io/2020/5/9/171f7861f6544a55?w=1620&h=536&f=png&s=141052)]
Allocations: 表示当前分配的数量
Shallow Size: 表示当前内存占用情况
可以看到我们的String[]占用最多,左键点击String[]后,右侧会出现所有的String[],然后左键点击任意一个,会出现Allocation Call Stack:内存分配的堆栈信息:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qG6fCUWG-1589018239606)(https://user-gold-cdn.xitu.io/2020/5/9/171f78b3a26c5377?w=1619&h=688&f=png&s=194039)]
可以很明显看到在我们代码里,是因为handleMessage:21, MainActivity$1 (com.leo.memoryanalyzertest),造成的内存抖动。右键这条,点击Jump to Source.可以跳转到我们的问题代码。这就是我们Profiler分析内存抖动的情况。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iUvBSwKD-1589018239607)(https://user-gold-cdn.xitu.io/2020/5/9/171f78deef594435?w=597&h=79&f=png&s=9879)]
1.3、如果用上篇的traceView方式,即CPU Profiler方式
首先我们推测下,traceView是分析卡顿利器,当然在这里能测出。但要知道它是偏向耗时情况的。上篇说过,这里就不说明了。同样点击CPU,然后record,stop记录一段。如图来到我们的Top Down:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6StOqYUE-1589018239608)(https://user-gold-cdn.xitu.io/2020/5/9/171f7d30377e0bf7?w=1616&h=432&f=png&s=76401)]
可以看到耗时时间在MessageQueue里,这也正和我们的内存抖动代码相关联,每间隔30ms发送一次,在handleMessage()里,也指明了代码在com.leo.memoryanalyzertest.MainActivity里。可以发现这种方式去分析,并不明显。
注意:我们都知道内存抖动,我们要寻找的话,也是找循环或者频繁调用的地方。所以这么看我们用CPU Profiler的方式也能大概确认问题代码的位置。
二、Memory Analyzer之分析内存泄漏
同样我们先创建一段内存泄漏的代码,先定义个接口
public interface CallBack {
void leoFun();
}
然后创建个静态list,添加CallBack实例
public class CallBackManager {
public static ArrayList<CallBack> sCallBacks = new ArrayList<>();
public static void addCallBack(CallBack callBack) {
sCallBacks.add(callBack);
}
public static void removeCallBack(Callback callback) {
sCallBacks.remove(callback);
}
}
再创建个BitmapActivity实现CallBack接口,设置一个大图main_bg,同时把实例假如到静态sCallBacks中,这样每次打开BitmapActivity然后退出,都会造成BitmapActivity不能被回收。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bitmap);
ImageView imageView = findViewById(R.id.image);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.main_bg);
imageView.setImageBitmap(bitmap);
CallBackManager.addCallBack(this);
}
这样我们不停打开这个页面然后关掉看内存情况,如图,可以看到内存直接上升:
红色框1:功能是主动发起一次gc。目的是回收一些可回收的,虚引用等。避免内存分析干扰
红色框2: 堆转储,将内存分配情况转换成hprof文件。(注意这里是studio的hprof文件,如果需要用Memory Analyzer分析,还要转换成标准的hprof文件)
点击右键Export,将文件存储在文件夹里。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UXAYTrwP-1589018239611)(https://user-gold-cdn.xitu.io/2020/5/9/171f804db306f156?w=406&h=202&f=png&s=11520)]
转换成mat能识别的hprof文件,只要cmd命令行来到我们的studio自带的hprof-conv.exe文件下,输入命令,即可转换
hprof-conv 源文件路径 输出文件路径,这样就生成了我们的my.hprof
2.1、怎么使用我们的Memory Analyzer
生成了hprof文件后,然后就是使用Memory Analyzer。
官网下载地址:https://www.eclipse.org/mat/downloads.php,因为捐赠什么的,不建议。我这找到了一个比较靠谱的版本,csdn下载
下载好后启动我们的MemoryAnalyzer.exe。File --> Open Heap Dump打开我们的my.hprof文件如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VultZtAS-1589018239616)(https://user-gold-cdn.xitu.io/2020/5/9/171f88c66a73a658?w=1672&h=866&f=png&s=74494)]
先简单介绍下Memory Analyzer的信息。
2.1.1、红色框1: OverView大概信息
- Size 内存大小
- Classes : class对象
- Objects : Objects个数,也就是实例个数
- Unreachable Objects Histogram :可被回收的对象,扔在内存当中
2.1.2、红色框2: Histogram直方图
- Objects: 这个类有多少个实例
- Shallow Heap: 单个实例占用的内存
- Retained Heap: 单个实例及其引用一共占有多少内存
我们可以利用直方图,里的Regex搜索我们最开始的内存泄漏案例。搜索BitmapActivity如图,可以看到我们的BitmapActivity有3个实例:
我们可以右键点击com.leo.me,oryanalyzertest.BitmapActivity --> List objects --> with incoming reference (谁引用了我),如图可以看到有3个地方引用了BitmapActivity:
此时我们右键一个实例点击 Path To GC Roots --> with all refrence,如图,找到文件下带小太阳的文件,可能触发内存溢出的地方。这里是说我们的sCallBacks引用了,也证实了在我们内存泄漏代码里的CallBackManager里的静态集合sCallBacks添加了它。
2.1.3、红色框3:dominator tree展示每个实例的内存和所占比例
可以看到我们这里的大头是bitmap。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z2MXW4F3-1589018239623)(https://user-gold-cdn.xitu.io/2020/5/9/171f869560338e9c?w=1448&h=209&f=png&s=32203)]
同样我们可以通过右键一个实例Path To GC Roots --> with all refrence如图,也能看到是我们的sCallBacks引用了:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YXLRYn0a-1589018239624)(https://user-gold-cdn.xitu.io/2020/5/9/171f86d44c60df13?w=716&h=387&f=png&s=43407)]
2.1.4、红色框4:OQL,类似于数据库搜索我们的内存情况
写入命令后,点击红色感叹号运行,如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HplpQ0dW-1589018239625)(https://user-gold-cdn.xitu.io/2020/5/9/171f870f6aa8ce57?w=670&h=350&f=png&s=25925)]
2.1.5、红色框5:thread overview,展示线程情况
2.1.6、红色框6:Top Consumers (重点),直接展示出我们内存中的大内存
展示我们内存中占有比较大的内存,可以看到这里都是被3个大bitmap占据着。直观利于分析
2.1.7、红色框7:Leak Suspects(重点),直接给出分析,分析出我们代码中可能会出现内存泄漏的地方
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0JPPBQZT-1589018239628)(https://user-gold-cdn.xitu.io/2020/5/9/171f879c46453d10?w=832&h=680&f=png&s=56188)]
这里我启动了3次BitmapActivity,它也给了我3个分析。点击details。看详情,也直接把问题定在了sCallBacks中:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-64JHGAkL-1589018239629)(https://user-gold-cdn.xitu.io/2020/5/9/171f87de82e08ede?w=873&h=445&f=png&s=55325)]
我的公众号
会发一些大白话实用的文章。也一直在寻找最有利的面试技巧。有想法的同学可以一起