Android中内存泄露的原因分析:
有垃圾回收机制,为什么还会出现内存泄露:
了解GC回收的原理:
GC会选择一些它了解还存活的对象作为内存遍历的根节点(GC Roots),比方说thread stack中的变量,JNI中的全局变量,zygote中的对象(class loader加载)等,然后开始对heap进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾,会被GC回收掉。
1,内存泄露和内存溢出:
内存泄露:
Java内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏。
内存溢出:
内存溢出(out of memory)通俗理解就是内存不够,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。此时软件或游戏就运行不了,系统会提示内存溢出,有时候会自动关闭软件。
Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。因此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现OutOfMemory的错误。
为什么会出现内存不够用的情况呢?我想原因主要有两个:
- 由于我们程序的失误,长期保持某些资源(如Context)的引用,造成内存泄露,资源造成得不到释放。
- 保存了多个耗用内存过大的对象(如Bitmap),造成内存超出限制。
区别:
内存泄露memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存迟早会被占
光。 是指向系统中申请分配内存进行使用(new),但使用完后,却没有归还。申请到的内存自己也不能再访问,而系统也不能再次将它分配给需要的程序。
内存溢出out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给他存了long才能存下的数。
比如栈:栈满时,进行进栈,内存溢出。栈空时,进行出栈,内存也会溢出。分配内存不足以放下数据项序列,称为内存溢出。
内存泄露会导致内存溢出。
2,导致内存泄露的原因分析 :
1,静态变量导致的内存泄露:
public class MainActivity extends Activity{
private static final String TAG = "MainActivity";
private static Context sContext;
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R,layout.main);
sContext = this;//sContext为静态变量,生命周期较长。导致当前activity无法销毁,因次静态sContext引用了它。导致内存泄露。
}
}
//由于用静态成员 TAG 缓存了drawable对象,所以activity加载速度会加快,但是这样做是错误的。
//因为在android 2.3系统上,它会导致activity销毁后无法被系统回收。
2,单例模式导致的内存泄露:
import java.util.ArrayList;
import java.util.List;
public class TestClass {
//创建 变量
private List<OnDataArrivedListener> mOnDataArrivedListeners = new ArrayList<OnDataArrivedListener>();
//创建类
private static class SingleHolder{
public static final TestClass INSTANCE = new TestClass();
}
//构造参数:
private TestClass(){}
//单例模式:
public static TestClass getInstance(){
return SingleHolder.INSTANCE; //调用帮助类的 常量
}
//添加同步:
public synchronized void registerListener(OnDataArrivedListener listener){
if(!mOnDataArrivedListeners.contains(listener)){
mOnDataArrivedListeners.add(listener);
}
}
//取消注册监听方法
public synchronized void unregisterListener(OnDataArrivedListener listener){
mOnDataArrivedListeners.remove(listener);
}
//定义数据接口:
public interface OnDataArrivedListener{
//定义方法
public void onDataArrived(Object obj);
}
}
调用上面的代码:
protected void on Create(Bundle savedInstance){
supper.onCreate(savedInstanced);
setContentView(R.layout.main);
TestClass.getInstance().registerListener(this);//activity实现onDataArrivedListener接口,并向TestClass注册监听。
//单例模式 的生命周期和Application保持一直,因此Activity对象无法被即时的释放。导致内存泄露。
}
3,属性动画导致的内存泄露:
属性动画中有一类无线循环的动画,如果在Activity中播放此类动画而且没有在onDestory中去 停止动画。那么动画会一直播放下去,尽管已经看不到动画效果了。并且这个时候Activity的View会被动画持有。而View又持有了Activity无法释放。下面动画是无线循环,会泄露当前的Activity,解决办法是在Activity的onDestory中调用animator.cancel();
animator.cancel();
4.Bitmap使用不当造成内存泄露:
当我们使用Bitmap对象开辟对象时,较大的Bitmap对象会被创建,其占用内存过多,超出堆的上限时,便会抛出内存泄露的危险。Bitmap对象不在使用时调用recycle()释放内存时会造成内存泄露。如果一个Bitmap对象比较占内存,当它不在被使用的时候,可以调用Bitmap.recycle()方法回收此对象的像素所占用的内存,但这不是必须的,视情况而定。
第一、及时的销毁,解决方案。
虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过Java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”。
//采样率方法一,简单设置为2:
BitmapFactory.Options newOpts= new BitmapFactory.Options();
newOpts.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
Bitmap bitmap =BitmapFactory.decodeFile(srcImgPath, newOpts);
//采样率方法二,循环原来的二倍:
Bitmap bm=null;
BitmapFactory.Options newOpts = new BitmapFactory.Options();
while(bm==null)
{
try {
bm = BitmapFactory.decodeFile(srcImgPath, newOpts);
break;
catch (OutOfMemoryError e)
{
e.printStackTrace();
}
newOpts.inSampleSize*=2;
}
//采样率方法三,循环直到不会抛出OutOfMemoryError:
Bitmap bm=null;
BitmapFactory.Options newOpts = new BitmapFactory.Options();
while(bm==null)
{
try {
bm = BitmapFactory.decodeFile(srcImgPath, newOpts);
break;
catch (OutOfMemoryError e)
{
e.printStackTrace();
}
newOpts.inSampleSize++;
}
//采样率方法四,根据图片文件大小阀值,比如100K:
newOpts.inSampleSize=(int) Math.ceil(Math.sqrt(fileLength/100*1024));
//采样率方法五,根据图片文件宽度和高度
int standardImgWidth=640;
int standardImgHeight=640;
int widthRation=width/standardImgWidth;
int heightRation=height/standardImgHeight;
newOpts.inSampleSize=(widthRation>heightRation)?heightRation:widthRation;
//采样率方法六,根据图片文件大小阀值/图片文件宽度和高度
rationSize=方法四获取的inSampleSize;
rationDimens=方法五获取的inSampleSize;
newOpts.inSampleSize=Math.min(rationSize, rationDimens);
5,handler造成的内存泄露:
handler:Handler持有对UI主线程消息队列MessageQueue和循环Looper的引用。子线程可以通过Handler将消息发送到UI线程的消息队列MessageQueue中。UI主线程通过Looper循环查询消息队列的UI_MQ,当发现有消息存在时会将消息从队列中取出。首先分析消息,通过消息的参数判断该消息对应的Handler,然后将消息分发到指定的Handler进行处理。
public class SampleActivity extends Activity {
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 发送一个10分钟后执行的一个消息
mHandler.postDelayed(new Runnable() {
@Override
public void run() { }
}, 600000);
// 结束当前的Activity
finish();
}
//当Activity结束后,在 Message queue 处理这个Message之前,它会持续存活着。这个Message持
//有Handler的引用,而Handler有持有Activity(SampleActivity)的引用,这个Activity所有的资
//源,在这个消息处理之前都不能也不会被回收,所以发生了内存泄露。
}