android 性能优化之内存泄漏
我们知道系统会给每个app分配的内存是有限的,这还和设备有关系,通过ActivityManager可以获取到分配的内存多少,
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
int memortClass = activityManager.getMemoryClass();
在我模拟器上是96M,在我华为手机上是128M,虽然系统给我们分配了这么多内存,但是app中如果加载图片过多就会吃很多内存,图片加载也是吃内存最多的.记得我项目中当时优化,主管在配置文件中使用这个
android:largeHeap="true"
但是我们也可以通过代码去获取
int largeMemoryClass = activityManager.getLargeMemoryClass();
08-06 16:20:24.508 17925-17925/com.example.optimize E/MainActivity: memortClass=128
08-06 16:20:24.508 17925-17925/com.example.optimize E/MainActivity: largeMemoryClass=256
但是这个值不是在所有的系统上都有用,今天主要讲下内存泄漏,百度下概念吧.
当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而就导致对象不能被回收。这种导致了本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏
这些对象是GC无法回收的,所以会造成内存泄漏,内存泄漏主要是要分析内存分配的情况:
1.静态的
静态的存储区:内存在程序编译的时候就已经分配好,这块的内存在程序整个运行期间都一直存在。
它主要存放静态数据、全局的static数据和一些常量,它的生命周期和app一样
2.栈式的
在执行函数(方法)时,函数一些内部变量的存储都可以放在栈上面创建,函数执行结束的时候这些存储单元就会自动被释放掉。
栈内存包括分配的运算速度很快,因为内置在处理器的里面的。当然容量有限。
3.堆式的
也叫做动态内存分配。有时候可以用new来申请分配一个内存。在C/C++可能需要自己负责释放(java里面直接依赖GC机制)。
在C/C++这里是可以自己掌控内存的,需要有很高的素养来解决内存的问题。java在这一块貌似程序员没有很好的方法自己去解决垃圾内存,需要的是编程的时候就要注意自己良好的编程习惯
关于栈与堆之间的区别:
堆是不连续的内存区域,堆空间比较灵活也特别大
栈式一块连续的内存区域,大小是有操作系统觉决定的
堆管理很麻烦,频繁地new/remove会造成大量的内存碎片,这样就会慢慢导致效率低下。对于栈的话,他先进后出,进出完全不会产生碎片,运行效率高且稳定
在java中什么东西存在栈中,什么存在堆中呢?
成员变量全部存储在堆中(包括基本数据类型,引用及引用的对象实体)---因为他们属于类,类对象最终还是要被new出来的。
局部变量的基本数据类型和引用存储于栈当中,引用的对象实体存储在堆中。-----因为他们属于方法当中的变量,生命周期会随着方法一起结束
java中有四大引用:
StrongReference强引用:
回收时机:从不回收 使用:对象的一般保存 生命周期:Davlik停止的时候才会终止
SoftReference,软引用
回收时机:当内存不足的时候;使用:SoftReference<String>结合ReferenceQueue构造有效期短;生命周期:内存不足时终止
WeakReference,弱引用
回收时机:在垃圾回收的时候;使用:同软引用; 生命周期:GC后终止
PhatomReference 虚引用
回收时机:在垃圾回收的时候;使用:合ReferenceQueue来跟踪对象呗垃圾回收期回收的活动; 生命周期:GC后终止
所以我们在对app进行优化的时候一定会用到软引用或者弱引用.特别是处理一些比较占用内存大并且生命周期长的对象的时候.
什么情况下app会产生内存泄漏呢?
1:单例使用上下文的时候
我们通常都有获取屏幕的宽和高的需求,一般是这么写的
package com.example.optimize; import android.content.Context; import android.util.DisplayMetrics; public class CommonUtil { private static CommonUtil instance; private Context context; private CommonUtil(Context context){ this.context = context; } public static CommonUtil getInstance(Context context){ if(instance == null){ instance = new CommonUtil(context); } return instance; } public int getWidth(){ DisplayMetrics packageMetrics =context.getResources().getDisplayMetrics(); return packageMetrics.widthPixels; } public int getHeight(){ DisplayMetrics packageMetrics =context.getResources().getDisplayMetrics(); return packageMetrics.heightPixels; } }使用:
package com.example.optimize; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity" ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e(TAG,"activity被创建了-----------"); setContentView(R.layout.activity_main); CommonUtil.getInstance(this); } @Override protected void onDestroy() { super.onDestroy(); Log.e(TAG,"activity被销毁了-----------"); } }现在通过as中自带的内存监听:
发现不动,内存还是一直比较正常的,主要是我们app没写啥东西.
上面的有二部分构成,一个是浅灰色也就是free,一个是淡蓝色是Allocated
free:表示没有被使用的内存
Allocated:已经分配的内存
现在free是0.29m,Allocated是3.1m,现在我们手动GC下,这按钮:
这个按钮就是initate GC,手动触发它,再看下内存情况:
现在free是0.39m Allocated是3m 对比之前没有手动的GC是回收了些内存
现在对屏幕进行旋转几下看看内存情况:
现在可以查看下它的内存快照,就是在手动GC旁边那个按钮,叫Dump java heap
你会看到内存中产生了5个MainActivty实例,这个时候看下log日记:
你会看到Activity确实走了onDestory()方法,但是为什么还有5个activity实例在呢?就是因为内存泄漏,它并没有被销毁.
就是因为这句话: CommonUtil.getInstance(this);
private static CommonUtil instance; private Context context; private CommonUtil(Context context){ this.context = context; } public static CommonUtil getInstance(Context context){ if(instance == null){ instance = new CommonUtil(context); } return instance; }
你会发现CommonUilt被定义成静态的,所以只有app进程挂了这个类才会消失,那MainActivity销毁了,这个还会在的,但是它引用了MainActivity,看下getInstance()方法,其实第二个acitvity传递进来的时候,因为instance这个变量不为null,所以context还是第一个MainActivity传递进来的上下文.这个时候再回头看看内存泄漏的概念,本来MainActivity会被回收,但是CommonUtil中持有它的引用(context),这样才会导致这个MainActivity其实并没有被GC回收,而造成了内存泄漏.
解决方案
很简单,就是使用application,让CommonUtil类生命周期和app一样就ok.
有时再补充完