JAVA是垃圾回收语言的一种,开发者无需特意管理内存分配。但是JAVA中还是存在着许多内存泄露的可能性,如果不好好处理内存泄露,会导致APP内存单元无法释放被浪费掉,最终导致内存全部占据堆栈(heap)挤爆进而程序崩溃。
JAVA是在JVM所虚拟出的内存环境中运行的,JVM的内存可分为三个区:堆(heap)、栈(stack)和方法区(method)。
说到内存泄露,就不得不提到内存溢出,这两个比较容易混淆的概念,我们来分析一下
大量的内存泄露会导致内存溢出(OOM)。
栈(stack)可以自行清除不用的内存空间。但是如果我们不停的创建新对象,堆(heap)的内存空间就会被消耗尽。所以JAVA引入了垃圾回收(garbage collection,简称GC)去处理堆内存的回收,但如果对象一直被引用无法被回收,造成内存的浪费,无法再被使用。所以对象无法被GC回收就是造成内存泄露的原因。
垃圾回收(garbage collection,简称GC)可以自动清空堆中不再使用的对象。在JAVA中对象是通过引用使用的。如果再没有引用指向该对象,那么该对象就无从处理或调用该对象,这样的对象称为不可到达(unreachable)。垃圾回收用于释放不可到达的对象所占据的内存。
public class MainActivity extends AppCompatActivity {
static Demo sInstance = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (sInstance == null) {
sInstance = new Demo();
}
sInstance.doSomething();
}
class Demo {
void doSomething() {
System.out.print("doSomething...");
}
}
}
上面的代码中的sInstance实例类型为静态实例,在第一个MainActivity act1实例创建时,sInstance会获得并一直持有act1的引用。当MainAcitivity销毁后重建,因为sInstance持有act1的引用,所以act1是无法被GC回收的,进程中会存在2个MainActivity实例(act1和重建后的MainActivity实例),这个act1对象就是一个无用的但一直占用内存的对象,即无法回收的垃圾对象。所以,对于lauchMode不是singleInstance的Activity, 应该避免在activity里面实例化其非静态内部类的静态实例。
private static Drawable sBackground;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
由于用静态成员sBackground 缓存了drawable对象,所以activity加载速度会加快,但是这样做是错误的。因为在Android 2.3系统上,它会导致activity销毁后无法被系统回收。
label .setBackgroundDrawable函数调用会将label赋值给sBackground的成员变量mCallback。
上面代码意味着:sBackground(GC Root)会持有TextView对象,而TextView持有Activity对象。所以导致Activity对象无法被系统回收。
下面看看android4.0为了避免上述问题所做的改进。
先看看android 2.3的Drawable.Java对setCallback的实现:
public final void setCallback(Callback cb){
mCallback = cb;
}
再看看android 4.0的Drawable.Java对setCallback的实现:
public final void setCallback(Callback cb){
mCallback = newWeakReference (cb);
}
在android 2.3中要避免内存泄漏也是可以做到的, 在activity的onDestroy时调用sBackgroundDrawable.setCallback(null)。
以上2个例子的内存泄漏都是因为Activity的引用的生命周期超越了activity对象的生命周期。也就是常说的Context泄漏,因为activity就是context。
想要避免context相关的内存泄漏,需要注意以下几点:
public class HandlerActivity extends Activity {
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 60000);
finish();
}
}
这样写没有问题?
AndroidStudio给我们有这样的提示
Handler 的生命周期与Activity 不一致
handler 引用 Activity 阻止了GC对Acivity的回收
修改代码如下:
public class HandlerActivity2 extends Activity {
private static final int MESSAGE_1 = 1;
private static final int MESSAGE_2 = 2;
private static final int MESSAGE_3 = 3;
private final Handler mHandler = new MyHandler(this);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 60000);
// just finish this activity
finish();
}
public void todo() {
};
private static class MyHandler extends Handler {
private final WeakReference mActivity;
public MyHandler(HandlerActivity2 activity) {
mActivity = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
System.out.println(msg);
if (mActivity.get() == null) {
return;
}
mActivity.get().todo();
}
}
}
注册广播接收器、注册观察者等等
以构造ListView的BaseAdapter为例,在BaseAdapter中提供了方法public View getView(int position,View convertView,ViewGroup parent)
来向ListView提供每一个item所需要的View对象。初始化时ListView会从BaseAdapter中根据当前屏幕布局实例
化一定数量的View对象,同时ListView会将这些View对象缓存起来。当向上滚动ListView时,原先位于最上面的
list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()
方法完成的,getView()的第二个形参View convertView就是被缓存起来的list item的View对象
由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪
费时间,也造成内存垃圾,给垃圾回收增加压力,如果垃圾回收来不及的话,虚拟机将不得不给应用进程分配更
多的内存,造成内存泄露。
虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过Java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”。
有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。如下面的代码:
ImageView preview;
BitmapFactory.Options options = newBitmapFactory.Options();
//图片宽高都为原来的二分之一,即图片为原来的四分之一
options.inSampleSize = 2;
Bitmap bitmap =BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);
preview.setImageBitmap(bitmap);
有些时候,我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。如下:
SoftReference bitmap_ref = new SoftReference(BitmapFactory.decodeStream(inputstream));
……
……
if (bitmap_ref .get() != null){
bitmap_ref.get().recycle();
}