一个好的性能优化,可以让你的软件运行速度上比别人快,出现的卡顿现象少,而且一个好的性能软件,会在系统内存中生存的更久。性能优化最主要的就是对Java内存的管理,即堆内存中的管理,对于Java内存分配的讲解,详细可见我的博客文章
内存泄漏和内存溢出的区别
成员变量和局部变量内存分配
工具的说明
我们通过一个单例模式的例子产生的内存泄漏来使用Android Monitor
/**
* 单例模式
*/
public class CommUtil {
private static CommUtil instance;
private Context context;
private CommUtil(Context context) {
this.context=context;
}
public static CommUtil getInstance(Context context){
if(null==instance){
instance=new CommUtil(context);
}
return instance;
}
}
/**
* 使用单例
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CommUtil instance = CommUtil.getInstance(this);
}
}
接着我们旋转三次屏幕,让Activity不断调用onCreate方法,从而导致CommUtil不断被实例化,在Android Monitor的内存监测中也能看得出来内存在增加
1、打开Android Monitor
2、点击Dump java Heap让其产生一份快照
3、找到MainActivity类,查看其实例情况
我们可以看到MainActvity有三个实例对象被引用
这里需要注意,旋转屏幕3次以上都只会有2个MainActivity。当GC回收的时候会将第0个和最后一个留着,其他的都会被回收
4、点击对象实例,找到引用的对象
5、解决方法
/**
* 使用单例
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CommUtil instance = CommUtil.getInstance(getApplicationContext());
}
}
这个时候,我们再按照前面的方法查看MainActivity的实例
正常的情况下,应用程序按返回键退出程序,并且经过多次手动GC,理论上所有的Activity都会被回收,我们可以通过Memory Usage的信息,查看退出程序后的Activity和View是否依然存在内存中,从此可以判断是否发生内存泄漏。我们主要是通过下面的入口,查看到最后程序退出并经过GC后的剩余情况,这里很明显可以看到内存泄漏了Activity和View,正常的值应该是0
我们通过计算斐波拉契列数的例子,学习TraceView的使用
//调用斐波拉契列数
computeFibonacci(40);
public int computeFibonacci(int positionInFibSequence) {
if (positionInFibSequence <= 2) {
return 1;
} else {
return computeFibonacci(positionInFibSequence - 1)
+ computeFibonacci(positionInFibSequence - 2);
}
}
1、打开Android Device Monitor (DDMS),按步骤启动TraceView
2、开始start Method Profiling之后,在主程序中执行斐波拉契列数,等待大概5秒就stop Method Profiling,系统会生成一份分析文件
3、鼠标移动到某一处黑色的地方
4、滑动鼠轮进行放大
5、产看详细信息面板
列名 | 作用 |
---|---|
Name | 该进程中所调用的函数名称 |
Incl Cpu Time | 函数占用的CPU时间,包含内部调用其它函数的CPU时间 |
Excl Cpu Time | 函数占用的CPU时间,但不包含内部调用其它函数所占用的CPU时间 |
Incl Real Time | 函数运行的真实时间(以毫秒为单位),内含调用其它函数所占用的真实时间 |
Excl Real Time | 函数运行的真实时间(以毫秒为单位),不包含调用其它函数所占用的真实时间 |
Calls+Recur Calls/Total | 函数被调用次数以及递归调用占总调用次数的百分比 |
Cpu Time/Call | 函数调用CPU时间与调用次数的比(该函数平均执行时间) |
Real Time/Call | 同CPU Time/Call类似,只不过统计单位换成了真实时间 |
6、解决方法
//将递归换为for循环 同时使用缓存 先将结果缓存起来
public int computeFibonacci(int positionInFibSequence) {
int prev=0;
int current=1;
int values;
for(int i=0;i
1、打开Android Monitor,找到下面按钮,点击start Allocation Tracking,然后执行我们的程序进行分配内存,最后stop Allocation Tracking,可以看到图片上有一段矩形,就是我们追踪的内存分配部分,等一会会生成一份分析报告
2、在详细报告中打开图形图,可以看到每一层的内存分布情况,后面的内存分配都可以在图形中结合右边介绍找到
1、采用Lint工具系统会检测出一些简单的内存泄漏,或者是书写规范等等问题,使用Analyze里面的Inspect Code
2、选择整个目录结构即可得到分析报告
1、静态变量引起的内存泄露情况
当调用getInstance时,如果传入的context是Activity的context,只要这个单例没有被释放,那么这个Activity也不会被释放,一直到进程退出才会释放。解决方法就是将传入的context设置为getApplicationContext()即可
public class CommUtil {
private static CommUtil instance;
private Context context;
private CommUtil(Context context) {
this.context = context;
}
public static CommUtil getInstance(Context mcontext) {
if (instance == null) {
instance = new CommUtil(mcontext);
}
return instance;
}
}
2、非静态内部类引起的内存泄露情况
由于非静态内部类对外部类持有应用,且非静态内部类的生命周期和外部类生命周期不一样,就会导致内存泄漏。解决方法就是将非静态内部类设置为静态内部类即可
3、注册的监听未移除引起的内存泄露情况
最常见的是 registerReceiver()、订阅-发布模式
4、资源未关闭引起的内存泄露情况
BroadCastReceiver、Cursor、Bitmap、IO流、自定义属性attribute、attr.recycle()的回收
5、无限循环动画引起的内存泄露情况
没有在onDestroy中停止动画,否则Activity就会变成泄露对象
1、Handler和Callback内存泄露
在Activity内部创建Handler,由于Activity的生命周期和Handler不一样,所以Handler会内存泄露,解决方法就是将Handler设置为静态类,有时Handler里面会存储Callback等变量,请务必将这些变量设置为弱引用进行存储
2、DCL(双检查锁机制)
DCL的单例需要对单例变量进行volatile修饰,否则JVM会存在指令的重排序优化,导致内存泄露