Android内存和垃圾回收
内存运行时数据区域分为:栈、堆、方法区、方法栈、程序计数器 。
GC 为了能够正确释放对象,GC 必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC 都需要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。
在Java 语言中,判断一块内存空间是否符合垃圾收集器收集标准的标准只有两个:一个是给对象赋予了空值null,以下再没有调用过,另一个是给对象赋予了新值,即 重新分配了内存空间。
内存泄露
内存泄漏指由于错误的设计造成程序未能释放已经不再使用的内存,造成资源浪费。GC会自动清理失去引用的对象所占用的内存。但是,由于程序设计错误而导致某些对象始终被引用,那么将会出现内存泄漏。
一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光、最终会导致out of memory、内存溢出、也就是OOM错误,程序崩溃。
常见的内存泄露
1.单例造成的内存泄漏
Android的单例模式,不过使用的不恰当的话也会造成内存泄漏。因为单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
解决方法有两种:
1:context应该传入ApplicationContext。
2:使用弱引用。
正确的代码如下:
public class AppManager {
private static AppManager instance;
//一种是弱引用
private WeakReference<Context> wr;
// private Context context;
public AppManager (Context context) {
wr = new WeakReference<>(context);
//第二种获取Application的Context
/**
这样不管传入什么Context最终将使用Application的Context,
而单例的生命周期和应用的一样长,这样就防止了内存泄漏
*/
// this.context = context.getApplicationContext();
}
public static AppManager getInstance(Context context) {
if (instance == null) {
instance = new AppManager (context);
}
return instance;
}
}
2.非静态内部类创建静态实例造成的内存泄漏
非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new TestResource();
}
//...
}
class TestResource {
//...
}
}
解决方法:
1、将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例
2、如果需要使用Context,请使用ApplicationContext 。
3.Handler造成的内存泄漏
mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadData();
}
private void loadData(){
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
正确的写法:
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}
4.线程造成的内存泄漏
匿名内部类对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。
//第一种
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
}.execute();
//第二种
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
正确的做法:
1、使用静态内部类的方式
2、Activity销毁时候也应该取消相应的任务AsyncTask::cancel(),避免任务在后台执行浪费资源
正确代码如下:
static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
//...
}
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(10000);
}
}
//——————
new Thread(new MyRunnable()).start();
new MyAsyncTask(this).execute();
5.资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap、webview、Timer、媒体类(MediaPlayer等)等资源的使用,应该在Activity或Fragment销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
参考资料:
http://www.bubuko.com/infodetail-1727148.html
图片资源优化
一、drawable目录的优化
1、大背景图能使用:.9.png图片的尽量使用
2、使用多分辨率图片设计、使用这种方式,还有好处就是可以降低峰值内存,优先避免内存溢出。因为在android中图片的加载会根据分辨率来自动缩放【缩放的过程会额外消耗内存】
3、很大图片资源放可以在assets或no-dpi中,也可以避免因缩放导致峰值内存过高 、因为内存只会加载一次,不会进行任何缩放
二、Bitmap 对象不在使用时调用 recycle()加快内存释放
三、使用三级缓存实现图片加载
使用LruCache 实现 内存–>sdcard–>网络 的图片加载
四、加载大图片的优化
首先inJustDecodeBounds设为true,只读取图片大小不加载,在计算图片需要压缩的比、在把inJustDecodeBounds设为flase读取压缩后的图片、避免内存溢出
常见的内存泄露案例
1、ondestroy方法已经反注册、但另起的线程返回时又注册了
2、Fragment跟activity相互引用,导致fragment无法释放
3、ImageLoader下载图片时错误传入上下文对象导致泄露
注意:下载图片的生命周期跟with传入的上下文一致,当在fragment里下载图片传入context时,fragment销毁了、下载图片的view还引用在activity里导致fragment无法释放
4、Activity直接引用fragment,fragment消耗后没有去掉activity引用
5、Fragment变成单例模式使用
关于一些内存泄漏的建议
1、对于生命周期比Activity长的对象如果需要应该使用ApplicationContext
2、在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景如下:
**其中:**NO1表示Application和Service可以启动一个Activity,不过需要创建一个新的task任务队列。而对于Dialog而言,只有在Activity中才能创建
1、对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏
2、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
- a.将内部类改为静态内部类
- b.静态内部类中使用弱引用来引用外部类的成员变量
3、对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null
4、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期
内存泄露检测工具
1、MAT
MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行分析,快速的计算出在内存中对象的占用大小,看看是谁阻止了垃圾收集器的回收工作,并可以通过报表直观的查看到可能造成这种结果的对象。
2、LeakCanary
如何使用LeakCanary
1、 build.gradle 中加入引用
dependencies {
debugCompile ‘com.squareup.leakcanary:leakcanary-android:1.3’
releaseCompile ‘com.squareup.leakcanary:leakcanary-android-no-op:1.3’
}
2、在Appliction中
LeakCanary.install(this);
3、监控可能泄露的对象,一般在activity和fragment destroy时监控
refWatcher.watch(object);
LeakCanary工作机制
1、RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。
2、然后在后台线程检查引用是否被清除,如果没有,调用GC。
3、如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。
4、在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。
5、得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄露。
6、HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。
7、引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。
详细内容参考我的另一篇文章:Android性能优化