Android内存管理
内存管理的目的就是让我们在开发中怎么避免我们的应用出现内存泄漏的问题。简单的来说,就是该释放的对象没有释放,一直被某个或某些实例持有却不再使用导致GC不能回收。我会从Java内存泄漏的基础知识开始,并通过具体的例子来说明Android引起内存泄漏的各种原因。
Java内存分配策略
Java程序运行的内存分配策略有3种,分别是静态分配、栈式分配、和堆式分配。对应的,3种存储策略使用的内存空间分别是静态存储区(方法区)、栈区和堆区。
- 静态存储区(方法区):主要存放静态数据、全局static数据和常亮。这块区域内存在编写程序时已经分配好,并且在程序整个运行期间都存在.
- 栈区: 当方法执行时,方法体内的局部变量(其中包括基础数据类型、对象的引用)都在栈上创建,并在方法执行结束后时这些局部变量所持有的内存将会自动释放。
- 堆区:又称动态内存分配,通常是指在程序运行时直接new出来的内存,也就是对象的实例。这部分内存在不使用时由Java垃圾回收器来负责回收。
堆和栈的区别
在方法体定义的(局部变量)一些基本类型的变量和对象的引用都是在方法的堆内存中分配的,当在一段方法中定义一个变量时,Java就会在栈中分配空间,当超过该变量的作用域后,该变量也就无效了,分配给它的内存空间也将被释放,该内存空间也可以重新被使用。
堆内存中用来存放所有由new创建的对象(包括该对象所有的成员变量)和数组。在堆中分配的内存,将有Java垃圾回收器来自动管理。在堆中产生一个数组和对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组和对象在堆内存的首地址,这个特殊的变量就是我们常说的引用变量。我们可以通过这个引用变量来访问堆中的对象或者数组。
举个例子:
public class Sample {
int s1 = 0;
Sample mSample1 = new Sample();
public void method() {
int s2 = 1;
Sample mSample2 = new Sample();
}
}
Sample 类的局部变量 s2 和引用变量 mSample2 都是存在于栈中,但 mSample2 指向的对象是存在于堆上的。
Java如何管理内存的
Java内存管理就是对象的分配和释放问题。在java中,程序员通过关键字new为每个对象申请内存空间,所有对象在堆中分配空间。另外,对象的释放有GC决定和执行的。
为了更好理解GC的工作原理,我们可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引用对象。例如:大多数程序从main入口开始执行的,那么该图就是以main进程顶点开始的一颗根树,跟顶点到达的对象都是有效对象,GC将不回收这些对象。如果某个对象和跟顶点不可达,我们就认为这些对象不再被引用,可以被GC回收。(此时的obj2为可回收对象)
什么是Java中的内存泄漏
在java中,内存泄漏的对象有两个特点:1.这些对象是可达的,在有向图中,存在通路可以相连,2.这些对象时无用的,程序以后不会再使用这些对象,如果对象满足这两个条件,这些对象可以认定Java中的内存泄漏,这些对象没有被GC回收,但还占用着对象。Android中的内存泄漏
- 集合类泄漏
集合如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存泄漏。如果这个集合类是全局性的变量(比如类中的静态属性),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。 - 单例造成的内存泄漏
public class AppManager {
private static AppManager mInstance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (mInstance == null) {
mInstance = new AppManager(context);
}
return mInstance;
}
}
这是个普通的单例模式,但是在创建单例时,需要传入一个Context,所以这个Context的生命周期的长短至关重要。
(1)如果此时传入的是Application的context,因为这个context对象是伴随着整个应用的生命周期,所以这没有任何问题。
(2)如果此时传入的是Activity的context,当这个context对应的Activity被销毁时,由于该context仍然被单例对象持有,所以这个Activity不会被回收,这就造成内存泄漏。
private AppManager(Context context) {
this.context = context.getApplicationContext();// 使用Application 的context
}
或者不用传入context
private AppManager() {
this.context = MyApplication.getContext();// 使用Application 的context
}
- 匿名内部类/非静态内部类和异步线程
有时候我们可能会在启动频繁的Activity中,为了避免创建相同的数据资源,可能会出现这种写法:
public class SampleActivity extends AppCompatActivity {
private static TestResource testResource;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (testResource == null) {
testResource = new TestResource();
}
}
/**
* 非静态内部类
*/
class TestResource {
}
}
这样在Activity中创建了一个非静态内部类,每次启动Activity·都会使用该类的数据,虽然这样避免了资源的重复创建,不过这种写法会造成内存泄漏,因为非静态内部类默认持有外部类,而该非静态内部类又创建了一个静态实例,该实例的生命周期和应用一样长,这就导致了一直持有Activity的引用,导致Activity不能回收,正确的做法应该是把内部类设置为静态或者将该内部类抽取出来封装成一个单例。但是Application的context不是万能的- 匿名内部类
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
exampleOne();
}
private void exampleOne() {
new Thread() {
@Override
public void run() {
while (true) {
SystemClock.sleep(1000);
}
}
}.start();
}
}
解决方法:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
exampleTwo();
}
private void exampleTwo() {
new MyThread().start();
}
private static class MyThread extends Thread {
@Override
public void run() {
while (true) {
SystemClock.sleep(1000);
}
}
}
}
通过上面的代码,新线程再也不会持有一个外部 Activity 的隐式引用,而且该 Activity 也会在配置改变后被回收。
- Handler造成的内存泄漏
public class SampleActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//TODO....
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
//TODO...
}
}, 1000 * 60 * 10);
finish();
}
}
在该SampleActivity 声明一个延迟10分钟后执行的消息,Hander的创建属于非静态内部类,持有Activity,当执行finish()方法后,Activity不会被回收,从而造成内存泄漏。
public class SampleActivity extends Activity {
private static class MyHandler extends Handler {
private final WeakReference<SampleActivity> mActivity;
public MyHandler(SampleActivity activity) {
mActivity = new WeakReference<SampleActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (activity != null) {
// ...
}
}
}
private final MyHandler mHandler = new MyHandler(this);
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() { /* ... */ }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
finish();
}
}
这里使用静态内部类+弱引用WeakReference 这种方式。