1.资源性对象未关闭
资源性对象(比如Cursor、File文件等)往往都用了一些缓冲,在不使用的时候,应该及时关闭它们,以便它们的缓存数据能够及时回收。它们的缓存不仅仅在JAVA虚拟机内,还存在JAVA虚拟机外,如果仅仅把它们的引用对象置为null,而不关闭它们,往往会造成内存泄露。因为有些资源性对象,比如SQLite Cursor,如果我们没有关闭它,而仅仅把它置为null,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象不再使用时,应该立刻调用它的close()函数,将其关闭,然后再置为null。在程序退出时,一定要确保其资源性对象已经关闭。
因此,在编写资源文件读写时,都需要在finally中关闭资源性对象,避免在异常情况下资源对象未被释放的隐患。
2.注册对象未注销
如果事件注册后未注销,会导致观察者列表中维持着对象的引用,阻止垃圾回收,一般发生在注册广播接受器、注册观察者等。
假设在Activity中,监听系统中的电话服务,以获取一些信息,可以在Activity中定义一个PhoneStateListener对象,同时将它注册到TelephonyManager服务中。对于Activity对象,理论上要求Activity在退出后该Activity对象就会被释放掉。
但是如果在释放该Activity时,忘记取消之前注册的PhoneStateListener对象,则会导致Activity无法被GC回收。如果不断地进出这个Activity,则最终会由于大量的Activity对象没有办法被回收而引起频繁的GC情况,甚至导致OOM。
3.类的静态变量持有大数据对象
静态变量长期维持着对象的引用,阻止垃圾回收,如果静态变量持有大的数据对象,如Bitmap等,就很容易引起内存不足等问题。
静态变量是在类被load的时候分配内存的,并且存在于方法区。当类被卸载的时候,静态变量被销毁。
1).类在什么时候被加载?
当我们启动一个app的时候,系统会创建一个进程,此进程会加载一个Dalvik VM的实例,
然后代码就运行在DVM之上,类的加载和卸载,垃圾回收等事情都由DVM负责。也就是说在进程启动的时候,类被加载,静态变量被分配内存。
2).类在什么时候被卸载?
在进程结束的时候。
4.非静态内部类的静态实例
非静态内部类会维持一个到外部类实例的引用,如果非静态内部类的实例是静态的,就会间接长期维持着外部类的引用,阻止被系统回收。如下代码是一个非静态内部类的实现。
public class LayoutPerActivity extends Activity {
private static TestModule mTestModule = null;
@override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.getWindow().setBackgroundDrawable(null);
setContentView(R.layout.activity_layout_per);
if(null == mTestModule) {
mTestModule = new TestModule(this);
}
}
class TestModule{
private Context mContext = null;
public TestModule(Context ctx) {
mContext = ctx;
}
}
}
在Activity内部创建了一个非静态内部类的静态实例mTestModule,每次启动Activity都会使用TestModule的数据,这样虽然避免了资源的重复创建,但是TestModule为非静态内部类默认持有外部类的引用,而这里又使用该非静态内部类创建了一个静态的实例,该实例的生命周期和应用一样长,这就导致该静态实例一直持有该Activity的引用,Activity的资源不能正常回收。
可以这样避免内存泄露,即将内部类TestModule设为静态内部类或者将内部类TestModule抽取出来封装成一个单例,如果需要使用Context,就在没有特殊要求的情况下,使用Application Context,如果需要使用Activity Context,就记得用完后置空让GC回收,否则还是会内存泄露。
5.Handler临时性内存泄露
Handler通过发送Message与主线程交互是应用开发中非常常见的使用场景之一,Message发出之后存在于MessageQueue中,有些Message也不是马上就被处理到。在Message中存在一个target,它是Handler的一个引用,Message在Queue中存在的时间过长,就会导致Handler无法被回收。如果Handler是非静态的,则会导致Activity或者Service不会被回收。Handler发送Message与主线程交互的实现代码如下:
public class LayoutPerActivity extends Activity {
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
private void doGetDataAsyncTask() {
Message msg = Message.obtain();
mHandler.sendMessage(msg);
}
}
由于mHandle是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,并且消息队列是在一个Looper线程中不断轮询处理消息,那就有一种情况,当这个Activity退出时,消息队列中还有未处理的消息或者正在处理的消息,并且消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄露。避免这种内存泄露需要修改以下三个地方;
1.使用一个静态Handler内部类,
2.对Handler持有的对象使用弱引用,这样在回收时,也可以回收Handler持有的对象
3.在Activity的onDestroy或者onStop时,应该移除消息队列中的消息,避免Looper线程的消息队列中有待处理的消息需要处理。
修改后的代码如下:
public class LayoutPerActivity extends Activity {
private NewHandler mHandler = new NewHandler(this);
private static class NewHandler extends Handler{
private WeakReference<Context> mContext = null;
public NewHandler(Context context) {
mContext = new WeakReference<Context>(context);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
private void doGetDataAsyncTask() {
Message msg = Message.obtain();
mHandler.sendMessage(msg);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}
6.容器中的对象没有清理造成的内存泄露
通常把一些对象的引用加入集合中,在不需要该对象时,如果没有把它的引用从集合中清理掉,这个集合就会越来越大。如果这个集合是static,情况就更严重了。
7.WebView造成的内存泄露
webview的内存泄露的解决方法一般是为此webview单独开一个进程,使用AIDL与应用的主进程进行通信,当不再使用此webView的时候杀死此进程即可。
附:检测内存泄露工具及集成方法
工具:square公司开源的Leakcanary
github地址:https://github.com/square/leakcanary.git
集成方法
添加依赖:
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1
新建一个App类继承Application
image.png
在onCreate方法中添加如下代码
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
然后在清单文件中配置App类
image.png就这样就集成好了
下面写一个栗子来验证一下吧:
在工程里新建一个Activity,取名字叫SecondActivity:
在SecondActivity添加如下代码:
public class SecondActivity extends AppCompatActivity {
private static SecondActivity.TestModule mTestModule = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.getWindow().setBackgroundDrawable(null);
setContentView(R.layout.activity_main);
if(null == mTestModule) {
mTestModule = new SecondActivity.TestModule(this);
}
}
class TestModule{
private Context mContext = null;
public TestModule(Context ctx) {
mContext = ctx;
}
}
}
然后在MainActivity中加一个按钮,用于打开SecondActivity
image.pngpublic class MainActivity extends AppCompatActivity implements View.OnClickListener{
private Button noStaticInnerClass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.getWindow().setBackgroundDrawable(null);
setContentView(R.layout.activity_main);
noStaticInnerClass = (Button) findViewById(R.id.no_static_inner_class);
noStaticInnerClass.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.no_static_inner_class:
startActivity(new Intent(this,SecondActivity.class));
break;
}
}
}
然后一直打开关闭SecondActivity,就会检测到内存泄露:
image.png
网友评论