内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光,即发生OOM;
static Vector v = new Vector(10);
for (int i = 1; i < 100; i++) {
Object o = new Object();
v.add(o);
o = null; // 设置null也无法将其内存回收,因为Vector中仍保存着对该内存的引用
}
设计上出现的问题,因为静态变量存储在方法区,因此instance的生命周期与应用相同,而instance中又保存了对A对象的引用,使得即使A对象不再使用,也无法对其进行释放。class A {} class Test { private static Test instance = new Test(new A()); private A a; private Test(A a) { this.a = a; } public Test getInstance() { return instance; } }
1、变量的作用域需要合理设置,类的成员变量生命周期与类对象相同,对于一些只需要短生命周期的变量,则会造成内存空间的浪费;class Server{ private String msg; public void recvMsg(){ readMsg(msg); save(msg); // 这里msg被保存之后已经无用,但是其生命周期将与Server保持一致,不能被回收,如果msg太大,将会造成不可预测的意外,安全的做法是加一行代码:msg=null; } }
而这个也需要与内存抖动之间进行平衡比较,避免短时间内创建大量的临时性变量而造成内存抖动。
2、同理对于static静态变量,也应当遵循谨慎的原则进行使用;
3、在Android中最突出的问题便是对Context的引用造成的内存泄露。因为Activity对象本身引用的资源较多,其无法进行释放造成的资源浪费效果将会很明显;因此应当注意避免让长生命周期的对象引用Activity,可以尝试引用Application Context;
5)内部类与外部类之间的引用造成的内存泄露;由于内部类保存着对外部类的引用,若未对其进行释放,外部类的内存空间也无法释放;
public class TestActivity extends Activity {
private MyHandler myHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// MyHandler是内部类,初始化MyHandler,其内部会隐式地保存对其外部类即Activity的引用
myHandler = new MyHandler();
// postDelayed一个Runnable,该操作会send一个Message到MessageQueue中,而Message对象msg中的
// target变量将会指向myHandler,即msg保存有对myHandler的引用
// 又MessageQueue是针对线程存在,当msg入队列还未得到处理时,Activity被关闭,此时msg依然存在与MessageQueue中
// 又由前面得知msg保存着对myHandler的引用,而myHandler保存着对Activity的应用;因此Activity关闭后,其内存仍然无法得到回收,即造成内存泄露
myHandler.postDelayed(new Runnable() {
@Override
public void run() {
}
}, 100000);
// 关闭Activity
finish();
}
private class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
}
}
}
由于
MyHandler
是内部类,初始化
MyHandler
,其内部会隐式地保存对其外部类即
Activity
的引用;
使用MyHandler postDelayed一个Runnable,该操作会send一个Message到MessageQueue中,而Message对象msg中的target变量将会指向myHandler,即msg保存有对myHandler的引用
又MessageQueue是针对线程存在,当msg入队列还未得到处理时,Activity被关闭,此时msg依然存在与MessageQueue中;
又由前面得知msg保存着对myHandler的引用,而myHandler保存着对Activity的应用;因此Activity关闭后,其内存仍然无法得到回收,即造成内存泄露。
2、解决Handler内存泄露的方法:使用弱引用及静态内部类的方法;
public class TestActivity extends Activity {
private MyHandler myHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myHandler = new MyHandler(this);
}
private static class MyHandler extends Handler {
// 与外部Activity建立弱引用关系
private WeakReference<TestActivity> mWeak;
public MyHandler(TestActivity activity) {
// 初始化
mWeak = new WeakReference<TestActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
TestActivity activity = mWeak.get();
if (activity != null) {
activity.handleMessage(msg);
}
}
}
// 处理事务逻辑
public void handleMessage (Message msg) {
// ......
}
}
6)涉及到ListView优化中的未使用convertView ,过多使用自己创建的View;
1、错误用法:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = inflater.inflate(R.layout.activity_main, parent, false);
// ......
return view;
}
2、由ListView的源码及工作原理 ,来看一下ListView的源码:
ListView填充及获取View的方法如下:
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
View child;
if (!mDataChanged) {
// Try to use an exsiting view for this position
child = mRecycler.getActiveView(position);
if (child != null) {
// Found it -- we're using an existing child
// This just needs to be positioned
setupChild(child, position, y, flow, childrenLeft, selected, true);
return child;
}
}
// Make a new view for this position, or convert an unused view if possible
child = obtainView(position, mIsScrap);
// This needs to be positioned and measured
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
由RecycleBin机制,ListView会首先尝试获取ActiveView[]中存储的View;然后再通过obtainView来获取View;
View obtainView(int position, boolean[] isScrap) {
isScrap[0] = false;
View scrapView;
scrapView = mRecycler.getScrapView(position);
View child;
if (scrapView != null) {
child = mAdapter.getView(position, scrapView, this);
if (child != scrapView) {
mRecycler.addScrapView(scrapView);
if (mCacheColorHint != 0) {
child.setDrawingCacheBackgroundColor(mCacheColorHint);
}
} else {
isScrap[0] = true;
dispatchFinishTemporaryDetach(child);
}
} else {
child = mAdapter.getView(position, null, this);
if (mCacheColorHint != 0) {
child.setDrawingCacheBackgroundColor(mCacheColorHint);
}
}
return child;
}
obtainView方法中首先通过getScrapView获取ScrapView[]中存储的View,ScrapView中存储的View便是划出界面废弃的View存储起来,便于再次重复利用;
可以看到在child = mAdapter.getView(position, scrapView, this);方法中来获得View,而scrapView即对应重写BaseAdapter方法中的convertView变量;不使用convertView则会造成废弃的View无法重复利用,同时大量创建的View,当View移出界面之后,而无法得到及时回收,造成内存泄露。
3、正确的用法:
private ViewHolder viewHolder;
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.activity_main, parent);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.imageView);
viewHolder.textView = (TextView) convertView.findViewById(R.id.textView);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
// ........
viewHolder.imageView.setImageDrawable(null);
return convertView;
}
static class ViewHolder {
TextView textView;
ImageView imageView;
}
7)资源没有关闭造成的内存泄露:
1、Cursor、File等资源型对象:
资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。
它们的 缓冲不仅存在于java虚拟机内,还存在于java虚拟机外。
如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。