Java是在JVM所虚拟出的内存环境中运行的,Android应用层是由Java开发的,Android的davlik虚拟机与jvm也类似,只不过它是基于寄存器的。内存分为三个区:堆、栈和方法区。
GC可以自动清理堆中不在使用(不在有对象持有该对象的引用)的对象。
在Java中对象如果在没有引用指向该对象,那么该对象就无从处理或调用该对象,这样的对象称为不可到达(unreachable)。垃圾回收用于释放不可到达的对象所占据的内存。
对Android来说,内存使用尤为吃紧,最开始的app进程最大分配才8M的内存,渐渐增加到16M、32M、64M,但是和服务端相比还是很渺小的,如果对象回收不及时,很容易出现OOM内存溢出错误。
内存泄漏(Memory leak): 当一个对象不再使用了,本应该被垃圾回收器回收,但是这个对象由于被其他正在使用的对象所持有,造成无法被回收的结果。这种对象存在堆内存中,就产生了内存泄漏。内存泄漏最终会导致内存溢出OOM
内存溢出(Out Of Memory):当APP占用的内存加上我们申请的内存资源超过了Dalvik虚拟机的最大内存时就会抛出的OOM异常。也就是说程序申请的内存超出了系统能给的内存大小。
内存抖动:在短时间内有大量的对象被创建或者被回收的现象,一般是在循环中大量创建或回收对象导致的。我们在开发中应当尽量避免这种情况。
三者关系:内存抖动 < 内存泄漏< 内存溢出
内存泄漏是造成内存溢出的主要原因之一,多次内存泄漏就会导致内存溢出。
其中内存溢出最为致命,可能会导致应用程序的退出,所以解决内存溢出很重要。
内存泄露主要表现为当Activity在finish的时候,由于对象持有对Activity的引用,造成Activity没有被及时回收。大致有以下5种情况造成内存泄露:
原因 :单例模式造成的内存泄漏,如context的使用,单例传入的是activity的context,在activity关闭后,因为单例持有activity的引用,activity的内存无法被回收。
解决:在context的使用上,应该传入application的context到单例模式中,这样就保证了单例的生命周期跟application的生命周期一样。单例模式应该尽量少持有生命周期不同的外部对象,一旦持有该对象的时候,必须在该对象的生命周期结束前null
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TestUtil testUtil = TestUtil.getInstance(this);
}
}
public class TestUtil {
private static TestUtil instance;
private Context context;
private TestUtil(Context context) {
this.context = context;
}
public static TestUtil getInstance(Context context) {
if (instance == null) {
instance = new TestUtil(context);
}
return instance;
}
}
单例模式修改为这样,通过获取context.getApplicationContext
拿到Application的context对象,保证单例中不管传入的是什么,其生命周期都是和应用一样长的,减少内存泄漏的可能性。
public class TestUtil {
private static TestUtil instance;
private Context context;
// context.getApplicationContext
private TestUtil(Context context) {
this.context = context.getApplicationContext();
}
public static TestUtil getInstance(Context context) {
if (instance != null) {
instance = new TestUtil(context);
}
return instance;
}
}
原因:当activity中创建的非静态内部类或者匿名内部类会持有Activity的隐式引用,Handler、Thread、AsyncTask、Runnable等,若内部类生命周期长于Activity(也就是当activity退出finish,但是内部类的异步任务尚未执行完成),会导致Activity实例无法被回收,造成内存泄漏。(屏幕旋转后会重新创建Activity实例,如果内部类持有引用,将会导致旋转前的实例无法被回收)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new AsyncTask<String, Void, String>() {
@Override
protected String doInBackground(String... params) {
for (int i = 0; i < 15; i++) {
try {
Log.e("MainActivity2", "dddd" + i + MainActivity2.this.getLocalClassName());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
}
}.execute();
}
}
在上面的代码中,存在一个非静态的匿名类对象AsyncTask,会隐式持有一个外部类的引用MainActivity 。同理,若是这个AsyncTask作为MainActivity的内部类而不是匿名内部类,他同样会持有外部类的引用。当MainActivity在这15秒内退出或者重建,AsyncTask异步任务尚未处理完成,会持有activity引用,导致其无法回收造成内存泄漏。
解决:如果一定要使用内部类,就改用static静态内部类,在内部类中通过WeakReference的方式引用外界资源。对Handler、Thread、Runnable等使用弱引用;或者在activity生命周期结束前调用removeCallbacksAndMessages
等移除异步任务,这样没有引用了自然可以回收。
举例:使用静态的Handler内部类,并通过WeakReference弱引用
public class MainActivity extends AppCompatActivity {
static MyHandler myHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myHandler = new MyHandler(this);
}
static class MyHandler extends Handler {
WeakReference<Activity> mActivityReference;
MyHandler(Activity activity) {
mActivityReference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
//....
}
}
}
}
在onPause()/onDestroy()方法中解除监听器,包括在Android自己的Listener,Location Service或Display Manager Service以及自己写的Listener。
原因:对于BraodcastReceiver,ContentObserver,Cursor,File,Stream,ContentProvider,Bitmap,动画,I/O,数据库,网络的连接等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
解决:
public class TestActivity extends AppCompatActivity {
private TextView textView;
ObjectAnimator objectAnimator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
textView = (TextView) findViewById(R.id.textview);
objectAnimator = ObjectAnimator.ofFloat(textView, "rotation", 0, 360);
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
objectAnimator.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 停止动画,防止内存泄漏
objectAnimator.cancel();
}
}
集合对象及时清理,使得JVM回收。我们通常会把对象存入集合中,当不使用时,清空集合,让相关对象不再被引用。
objectList.clear();
objectList = null;
原因:静态变量Activity和View会导致内存泄漏。例如下面代码中的静态变量context,textView的生命周期与应用的生命周期一样,而他们都持有当前Activity的(TestActivity )引用,一旦TestActivity 销毁,而它的引用一直被持有,就不会被回收,内存泄漏就产生了。
解决:static对象的生命周期过长,应该谨慎使用,一定要使用要及时进行null处理。
public class TestActivity extends AppCompatActivity{
private static Context context;
private static TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
textView = new TextView(this);
}
}
原因:我们都知道,threadlocal中维护了一个ThreadLocalMap,而threadlocal作为map的值弱引用存在,当threadlocal为null的时候,垃圾回收机制回收,但是此时我们的threadlocalmap与thread生命周期一致,不会被回收,ThreadLocalMap的key没了,但是value还在,这样就会导致内存泄漏。
解决:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。
产生内存溢出的原因主要有:
建议使用第三方,或者JNI来进行处理;
// Bitmap对象没有被回收
if (!bitmapObject.isRecyled()) {
// 释放
bitmapObject.recycle();
// 提醒系统及时回收
System.gc();
}
当频繁大量的创建/回收对象的时候,会造成内存抖动,会导致GC处理机制频繁的运行。
因为new对象会在内存中引用大量内存地址,如果对象大量并且没有被回收的时候,当需要大片的连续地址时,内存中空间足够,但是提供不了连续的内存,也会导致内存溢出。