内存泄漏分析,工具,泄漏的情况

一:内存泄漏的概念

内存泄漏:内存不再GC的掌控中。也就是一个对象不再需要使用,本该被回收时,但是有另外一个对象持有该对象,导致对象不能回收。这种导致本该回收的对象不能回收且停留在堆内存中,这就是内存泄漏。

内存分配策略:

  • 静态内存:存放静态数据,这块内存是在编译时就已经分配好的,在整个运行期间都存在。他主要存放静态数据、全局的static数据和一些常量。
  • 栈内存:程序执行是,局部变量的创建存储区,执行结束后会自动释放。
  • 堆内存:也叫动态内存分配。有时可以用malloc或者new来申请分配一个内存。在C/C++可能需要自己负责释放(java里面直接依赖GC机制)。在C/C++这里是可以自己掌控内存的,需要有很高的素养来解决内存的问题。java在这一块貌似程序员没有很好的方法自己去解决垃圾内存,需要的是编程的时候就要注意自己良好的编程习惯。

区别:1:堆是不连续的内存区域,堆空间比较灵活也特别大。
           2: 栈式一块连续的内存区域,大小是有操作系统觉决定的。

           3:堆管理很麻烦,频繁地new/remove会造成大量的内存碎片,这样就会慢慢导致效率低下。对于栈的话,他先进后出,进出完全不会产生碎片,运行效率高且稳定。

public class Student{
    int a = 1;
    Dimen  d1= new Dimen ();
    public void XXX(){
        int b = 1;//栈里面
        Dimen d2 = new Dimen ();
    }

}

1.成员变量全部存储在堆中(包括基本数据类型,引用及引用的对象实体)---因为他们属于类,类对象最终还是要被new出来的。
2.局部变量的基本数据类型和引用存储于栈当中,引用的对象实体存储在堆中。-----因为他们属于方法当中的变量,生命周期会随着方法一起结束。

 

内存泄露,主要讨论堆内存,他存放的就是引用指向的对象实体。

内存泄露(Memory Leak):
    进程中某些对象已经没有使用价值了,但是他们却还可以直接或者间接地被引用到GC Root导致无法回收。
    当内存泄露过多的时候,再加上应用本身占用的内存,日积月累最终就会导致内存溢出OOM.
内存溢出(OOM):    
    当应用占用的heap资源超过了Dalvik虚拟机分配的内存就会内存溢出。比如:加载大图片。

二:内存泄漏的情况

OutOfMenoryError主要有以下几种情况:

1:在ListView或者GridView 等加载大量数据或图片,构造adapter没有使用缓存ContentView

@Override  
public View getView(int position, View convertView, ViewGroup parent) {  
    ViewHolder vHolder = null;  
    //如果convertView对象为空则创建新对象,不为空则复用  
    if (convertView == null) {  
        convertView = inflater.inflate(..., null);  
        // 创建 ViewHodler 对象  
        vHolder = new ViewHolder();  
        vHolder.img= (ImageView) convertView.findViewById(...);  
        vHolder.tv= (TextView) convertView  
                .findViewById(...);  
        // 将ViewHodler保存到Tag中  
        convertView.setTag(vHolder);  
    } else {  
        //当convertView不为空时,通过getTag()得到View  
        vHolder = (ViewHolder) convertView.getTag();  
    }  
    // 给对象赋值,修改显示的值  
    vHolder.img.setImageBitmap(...);  
    vHolder.tv.setText(...);  
    return convertView;  
}  

static class ViewHolder {  
    TextView tv;  
    ImageView img;  
} 

   图片非常占用内存,一定要管理好内存,不然很容易内存溢出。
    滑出去的图片就回收,节省内存。看ListView的源码----回收对象,还会重用ConvertView。
    如果用户反复滑动或者下面还有同样的图片,就会造成多次重复IO(很耗时),
    那么需要缓存---平衡好内存大小和IO,算法和一些特殊的java类。
    算法:lrucache(最近最少使用先回收)
    特殊的java类:利于回收,StrongReference,SoftReference,WeakReference,PhatomReference
        
StrongReference强引用:
    回收时机:从不回收 使用:对象的一般保存  生命周期:JVM停止的时候才会终止
SoftReference,软引用
    回收时机:当内存不足的时候;使用:SoftReference<String>结合ReferenceQueue构造有效期短;生命周期:内存不足时终止
WeakReference,弱引用
    回收时机:在垃圾回收的时候;使用:同软引用; 生命周期:GC后终止
PhatomReference 虚引用
    回收时机:在垃圾回收的时候;使用:合ReferenceQueue来跟踪对象呗垃圾回收期回收的活动; 生命周期:GC后终止

开发时,为了防止内存溢出,处理一些比较占用内存大并且生命周期长的对象的时候,可以尽量使用软引用和弱引用。
软引用比LRU算法更加任性,回收量是比较大的,你无法控制回收哪些对象。

比如使用场景:默认头像、默认图标。
ListView或者GridView等要使用内存缓存+外部缓存(SD卡)

2:资源为关闭引起的内存泄漏

BroadCastReceiver:registerReceiver()unregisterReceiver()要成对出现,通常需要在ActivityonDestory()方法去取消注册广播接收者。

Cursor:操作完close。

Bitmap:使用后未调用recycle()。

IO流:用完后及时关闭。

自定义属性attribute:attr.recycle()回收。


当不需要使用的时候,要记得及时释放资源。否则就会内存泄露。

3:不需要用的监听未移除会发生内存泄露

  例子1:
//        tv.setOnClickListener();//监听执行完回收对象
        //add监听,放到集合里面
        tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
            @Override
            public void onWindowFocusChanged(boolean b) {
                //监听view的加载,view加载出来的时候,计算他的宽高等。

                //计算完后,一定要移除这个监听
                tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
            }
        });

    例子2:

       SensorManager sensorManager = getSystemService(SENSOR_SERVICE);
        Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
        sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);
        //不需要用的时候记得移除监听
        sensorManager.unregisterListener(listener);

4:集合类泄漏

集合类中如果只有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量(比如类中的静态属性),那么没有 响应的删除机制,很可能导致集合所占用的内存只增不减。

5:无限循环动画
没有在onDestroy中停止动画,否则Activity就会变成泄露对象。
比如:轮播图效果。

6:Context泄漏 这是一个很隐晦的OutOfMemoryError的情况。先看一个Android官网提供的例子:

private static Drawable sBackground;  
@Override  
protected void onCreate(Bundle state) {  
	super.onCreate(state);  
	TextView label = new TextView(this);  
	label.setText("Leaks are bad");  
	if (sBackground == null) {  
		sBackground = getDrawable(R.drawable.large_bitmap);  
	}  
	label.setBackgroundDrawable(sBackground);  
	setContentView(label);  
}  

这段代码效率很快,但同时又是极其错误的:
我们看一下setBackgroundDrawable(Drawable background)的源码:

 public void setBackgroundDrawable(Drawable background) {
	...
	background.setCallback(this);
 }

background.setCallback(this);方法,也就是说Drawable拥有TextView的引用,而TextView又拥有Activity*(Context类型)*的引用, 因为sBackgroundstatic的,即使Activity被销毁,但是sBackground的生命周期还没走完,所以内存仍然不会被释放。这样就会有内存泄露了。 对,这样想是对的,但是我们看一下setCallback的源码:

  public final void setCallback(Callback cb) {
    mCallback = new WeakReference<Callback>(cb);
}

我们会发现里面使用了WeakReference,所以不会存在内存泄露了,但是官网当时提供的例子明明说有泄露,这是因为在3.0之后, 修复了这个内存泄露的问题,在3.0之前setCallback,方法是没有使用WeakReference的,所以这种泄露的情况在3.0之前会发生,3.0之后已经被修复。

7: 线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。

public class MyActivity extends Activity {     
	@Override     
	public void onCreate(Bundle savedInstanceState) {         
		super.onCreate(savedInstanceState);         
		setContentView(R.layout.main);         
		new MyThread().start();     
	}       
	private class MyThread extends Thread{         
	@Override         
		public void run() {             
		super.run();             
		//耗时的操作       
		}     
	} 
}  

假设MyThreadrun函数是一个很费时的操作,当调用finish的时候Activity会销毁掉吗?
事实上由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThreadrun函数没有结束时,MyThread是不会被销毁的, 因此它所引用的Activity也不会被销毁,因此就出现了内存泄露的问题。

8:单例造成的内存泄漏
由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏,比如:

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;
	}
}

这里如果传入的是ActivityContext,当该ContextActivity退出后,由于其被单例对象引用,所以会导致Activity无法被回收,就造成内存泄漏。

:9:尽量使用ApplicationContext Context引用的生命周期超过它本身的生命周期,也会导致Context泄漏。 所以如果打算保存一个长时间的对象时尽量使用Application这种Context类型。 例如:

mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
改成:
mStorageManager = (StorageManager) getApplicationContext().getSystemService(Context.STORAGE_SERVICE);

按道理来说这种系统服务是不会有问题,但是有些厂商在修改的时候,可能会导致Context无法被及时释放。

10:Handler的使用,在Activity退出的时候注意移除(尤其是循环的时候)

public class ThreadDemo extends Activity {  
	private static final String TAG = "ThreadDemo";  
	private int count = 0;  
	private Handler mHandler =  new Handler();  
	  
	private Runnable mRunnable = new Runnable() {  
		  
		public void run() {  
			//为了方便 查看,我们用Log打印出来   
			Log.e(TAG, Thread.currentThread().getName() + " " +count);    
			//每2秒执行一次   
			mHandler.postDelayed(mRunnable, 2000);  
		}   
	};  
	@Override  
	public void onCreate(Bundle savedInstanceState) {  
		super.onCreate(savedInstanceState);  
		setContentView(R.layout.main);   
		//通过Handler启动线程   
		mHandler.post(mRunnable);  
	} 
} 

这样在也会引发内存泄露。我们应该在onDestory方法中移除Handler,代码如下:

@Override  
protected void onDestroy() {  
	super.onDestroy();  
	mHandler.removeCallbacks(mRunnable);  
} 

 

11:由上面的Handler可以引伸出来的匿名内部类、非静态内部类和异步线程导致的内存泄漏。
下面看一个非静态内部类创建静态实例导致的内存泄漏

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 {
		//...
	}
}

因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态实例,该实例的声明周期与应用的一样长, 这就导致了静态实例一直会持有Activity的引用而造成内存泄漏。
下面再看一个匿名内部类和异步现成的现象:

public class MainActivity extends Activity {
	...
	Runnable ref1 = new MyRunable();
	Runnable ref2 = new Runnable() {
		@Override
		public void run() {

		}
	};
	...
}

上面的例子中ref1对象是没问题的,但是ref2这个匿名类的实现对象中有外部类的引用,如果此时线程的生命周期与Activity的不一致时就会造成了泄漏。

总结一下避免Context泄漏应该注意的问题:

  • 使用getApplicationContext()类型。
  • 注意对Context的引用不要超过它本身的生命周期。
  • 慎重的使用static关键字。
  • Activity里如果有线程或Handler时,一定要在onDestroy()里及时停掉。

三:内存泄漏的判断的工具

性能优化的帮助工具:
    MAT,
    Memory Monitor(属于AndroidMonitor中一个模块),
    HeapTool(查看堆信息),
    Allaction Tracking,
    LeakCanary
    Lint工具

1:Allaction Tracking
追踪内存分配信息。可以很直观地看到某个操作的内存是如何进行一步一步地分配的。


2:LeakCanary
Square公司
可以直接在手机端查看内存泄露的工具
实现原理:本质上还是用命令控制生成hprof文件分析检查内存泄露。
然后发送通知。
Application
    install()
LeakCanary
    androidWatcher()
RefWatcher
    new AndroidWatcherExecutor() --->dumpHeap()/analyze()(--->runAnalysis())--->Hprof文件分析
    new AndroidHeapDumper()
    new ServiceHeapDumpListener


3:Lint分析工具
Android Studio很方便 很好用。
    检测资源文件是否有没有用到的资源。
    检测常见内存泄露
    安全问题SDK版本安全问题
    是否有费的代码没有用到
    代码的规范---甚至驼峰命名法也会检测
    自动生成的罗列出来
    没用的导包
    可能的bug

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值