Github地址:新闻类App (MVP + RxJava + Retrofit+Dagger+ARouter)
关于内存优化,之前写过一篇文章,android性能优化之内存优化,大家可以先看下这篇文章
内存问题
内存抖动:图形是锯齿状,GC导致卡顿
内存泄漏:可用内存减少,不断的GC
内存溢出:OOM异常,程序异常
内存优化工具
工具使用大家可以找相关资料,这里我并不介绍工具的使用
Memory Profiler
1.实时图表表示应用内存使用量,可以识别内存泄漏,内存抖动等
2.提供捕获堆转储,强制GC以及跟踪内存分配的能力
Memory Analyzer
MAT:强大的java Heap分析工具,查找内存泄漏以及内存占用
生成整体报告,分析问题等
线下使用
LeakCanary
自动检查内存泄漏工具
Github:https://github.com/square/leakcanary
内存管理机制
-
Java内存管理机制
java内存回收算法
标记-清除算法
- 标记出所有需要回收的对象
- 统一回收所有被标记的对象
缺点
- 效率不高
- 产生大量不连续的内存碎片
复制算法
- 将内存划分为大小相等的两块
- 一块内存用完之后复制存活对象到另一块
- 清理另一块内存
优缺点
- 相对标记清除算法,实现简单,运行高效
- 浪费一半空间,代价大
标记-整理算法
- 标记过程与“标记-清除算法”一样
- 存活对象往一端进行移动
优缺点
- 避免标记-清除导致的内存碎片
- 避免复制算法的空间浪费
分代收集算法
- 结合多种收集算法优势
- 新生代对象存活率低,可用复制算法
- 老年代对象存活率高,可用标记-整理
Android内存管理机制
- 内存弹性分配,分配值与最大值受具体设备影响
- OOM场景:内存真正不足或者可用内存不足
Dalivk与Art区别
- Dalivk仅固定一种回收算法
- Art回收算法可运行期选择
- Art具有 内存整理能力,减少内存空间
Low Memory killer
- 进程分类
前台进程,可见进程,服务进程,后台进程,空进程
内存抖动介绍
定义:内存频繁分配和回收导致内存不稳定
表现:频繁GC,内存曲线呈锯齿状
危害:导致卡顿,OOM异常
为什么内存抖动导致OOM
- 频繁创建对象,导致内存不足及碎片
- 不连续的内存无法被分配
内存抖动实战
首先使用Memory Profiler初步排查
使用Memory Profiler或CPU Profiler结合代码排查
模拟内存抖动代码
public class MemoryShakeActivity extends AppCompatActivity implements View.OnClickListener {
@SuppressLint("HandlerLeak")
private static Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 创造内存抖动
for (int index = 0; index <= 100; index++){
String arg[] = new String[100000];
}
mHandler.sendEmptyMessageDelayed(0,30);
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory);
findViewById(R.id.bt_memory).setOnClickListener(this);
}
@Override
public void onClick(View v) {
mHandler.sendEmptyMessage(0);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}
memory profiler出现以下情况
点击工具按钮的红色圆圈按钮,过一段时间再次点击停止记录
过一段时间下方会出现以下的情况
我们发现内存消耗最多的String[]点击String[].并选择其中的一个String[]
双击handleMessage就会跳转到相关代码
内存泄漏实战
定义:内存中存在已经没有用的对象
表现:内存抖动,可用内存逐渐变少
危害:内存不足,GC频繁,OOM异常
代码实战
public class MemoryLeakActivity extends AppCompatActivity implements CallBack{
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memoryleak);
ImageView imageView = findViewById(R.id.iv_memoryleak);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.avatar);
imageView.setImageBitmap(bitmap);
CallBackManager.addCallBack(this);
}
@Override
public void dpOperate() {
}
}
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);
}
}
public interface CallBack {
void dpOperate();
}
从某个页面进去多进去几次之后会出现内存泄漏,memory profiler此时只能判断是否内存泄漏,解决还得使用MAT工具,
MAT下载地址https://www.eclipse.org/mat/
首先点击1然后点击2导出文件,但是导出的文件需要进行转换,我们需要找到自己的android sdk目录下的platform-tools目录中找到hprof-conv.exe文件,然后cmd命令找到platform-tools目录,执行命令
hprof-conv 源文件 输出文
我的是:
hprof-conv C:\Users\asus\Desktop\result.hprof C:\Users\asus\Desktop\result1.hprof
打开MAT->打开文件->Overview->Histogram->搜索MemoryLeakActivity
搜索MemoryLeakActivity之后的结果,此时有Objects有代表的确有内存泄漏8个
右击选择ListObjects->With incoming references
继续右击
结果如下
代表CallBackManager中sCallBacks持有了MemoryLeakActivity对象
解决办法MemoryLeakActivity中添加
@Override
protected void onDestroy() {
super.onDestroy();
CallBackManager.removeCallBack(this);
}
检测不合理图片
Bitmap内存模型
- API10之前Bitmap自身在Dalivk Heap中,像素在Native中
- API10之后像素也放在Dalivk heap中
- API26之后像素在Native中
获取Bitmap占用的内存
- getByteCount
- 宽高一像素占用的内存
常规方式
- 背景:图片的宽高大于控件宽高
- 实现:继承ImageView,覆写实现计算大小
ARTHook
- 挂钩,将额外的代码钩住原有的方法,修改执行逻辑
- 框架:Epic(不能带到线上环境)
- 代码
ImageHook工具类
public class ImageHook extends XC_MethodHook {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
//实现逻辑
ImageView imageView = (ImageView) param.thisObject;
checkBitmap(imageView, imageView.getDrawable());
}
private static void checkBitmap(Object thiz, Drawable drawable) {
if (drawable instanceof BitmapDrawable && thiz instanceof View) {
final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
if (bitmap != null) {
final View view = (View) thiz;
int width = view.getWidth();
int height = view.getHeight();
if (width > 0 && height > 0) {
// 图标宽高都大于view带下的2倍以上,则警告
if (bitmap.getWidth() >= (width << 1)
&& bitmap.getHeight() >= (height << 1)) {
warn(bitmap.getWidth(), bitmap.getHeight(), width, height, new RuntimeException("Bitmap size too large"));
}
} else {
final Throwable stackTrace = new RuntimeException();
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
int w = view.getWidth();
int h = view.getHeight();
if (w > 0 && h > 0) {
if (bitmap.getWidth() >= (w << 1)
&& bitmap.getHeight() >= (h << 1)) {
warn(bitmap.getWidth(), bitmap.getHeight(), w, h, stackTrace);
}
view.getViewTreeObserver().removeOnPreDrawListener(this);
}
return true;
}
});
}
}
}
}
private static void warn(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight, Throwable t) {
String warnInfo = new StringBuilder("Bitmap size too large: ")
.append("\n real size: (").append(bitmapWidth).append(',').append(bitmapHeight).append(')')
.append("\n desired size: (").append(viewWidth).append(',').append(viewHeight).append(')')
.append("\n call stack trace: \n").append(Log.getStackTraceString(t)).append('\n')
.toString();
LogUtils.e(warnInfo);
}
}
App中调用
DexposedBridge.hookAllConstructors(ImageView.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
//setImageBitmap 方法名字 Bitmap 参数类型
DexposedBridge.findAndHookMethod(ImageView.class,"setImageBitmap",
Bitmap.class,new ImageHook());
}
});
线上内存泄漏监控
常规实现一
- 设定场景线上Dump:Debug.dumpHprofData();
- 实现流程
超过最大内存的80%->内存Dump->回传文件->MAT手动分析 - 缺点:上传失败率高,分析困难
常规实现二
- leakCanary带到线上
- 预设泄漏怀疑点
- 发现泄漏回传
- 缺点:不适合所有情况,必须预设怀疑点
LeakCanary原理(源码:后期我会单独写篇文章)
- 监控生命周期,onDestory添加RefWatch检测
- 二次确认断定发生内存泄漏
- 分析泄漏,找引用链
- 监控组件+分析组件
LeakCanary定制 - 预设怀疑点->自动找怀疑点(找内存大的)
- 分析泄漏链路慢(原因它会分析每个链路)->分析 Retain size大的对象
- 分析OOM(leakcanary会把所有文件加载到内存)->对象裁剪,不全部加载到内存中
线上监控完整方案
- 待机内存,重点模块内存,oom率
- 整体及重点模块GC次数,GC时间
- 增强的LeakCanary自动化内存泄漏分析