androidandroid-activitymemory-managementandroid-memory

How to force a crash from memory leak on Android?


So, I often hear that "holding on to a static activity or view, especially inside an AsyncTask that is long running will cause a memory leak and crash your app".

However, I've been unsuccessfully able to actually prove that in an Android Emulator.

What am i doing wrong?

public class MainActivity extends AppCompatActivity {

static TextView label;
static List<Activity> sHolder = new ArrayList<>();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ViewGroup vg = findViewById(R.id.blah);
    for (int i=0; i<1000000; i++) {
        ImageView im = new ImageView(this);
        im.setImageDrawable(getApplicationContext().getDrawable(R.drawable.kitten_original));
        vg.addView(im);

        new MyTask(this).execute();
    }

    sHolder.add(this);
}

@Override
protected void onStart() {
    super.onStart();
}

@Override
protected void onStop() {
    super.onStop();
}

@Override
protected void onDestroy() {
    super.onDestroy();
}

class MyTask extends AsyncTask<Void, Void, Void> {

    Activity activity;
    public MyTask(Activity activity) {
        this.activity = activity;
    }
    @Override
    protected Void doInBackground(Void... voids) {
        try {
            Thread.sleep(10000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Rather than every crashing, My computer just starts lagging a little bit, and it keep seeing like 100 MB of Garbage collection happening.

How can i actually force an app to crash? (Please don't ask why i want to force an app to crash, i'm doing some limit testing)


Solution

  • I often hear that "holding on to a static activity or view, especially inside an AsyncTask that is long running will cause a memory leak and crash your app".

    That is a simplified explanation.

    However, I've been unsuccessfully able to actually prove that in an Android Emulator.

    I suspect that this code is not running. You should crash after a couple of hundred passes through the loop, as there is a limit on how many queued-up AsyncTasks you can have, and that limit is not tied to memory consumption.

    Ignoring the million AsyncTasks and the million ImageViews, you are leaking the activity by means of sHolder, much as how in this sample I leak an activity by holding a static reference to a Button from its layout:

    /***
     Copyright (c) 2015 CommonsWare, LLC
     Licensed under the Apache License, Version 2.0 (the "License"); you may not
     use this file except in compliance with the License. You may obtain    a copy
     of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
     by applicable law or agreed to in writing, software distributed under the
     License is distributed on an "AS IS" BASIS,    WITHOUT WARRANTIES OR CONDITIONS
     OF ANY KIND, either express or implied. See the License for the specific
     language governing permissions and limitations under the License.
    
     Covered in detail in the book _The Busy Coder's Guide to Android Development_
     https://commonsware.com/Android
     */
    
    package com.commonsware.android.button;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.widget.Button;
    
    public class ButtonDemoActivity extends Activity {
      private static Button pleaseDoNotDoThis;
    
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    
        pleaseDoNotDoThis=(Button)findViewById(R.id.button1);
      }
    }
    

    You can demonstrate that a leak occurs using LeakCanary, Android Studio's heap analyzer, etc. However, to demonstrate that leak, you need to run the app, then press BACK, and see that your destroyed activity is not garbage-collected. Or, you need to run the app, rotate the screen (or undergo any other type of configuration change), and see that you now have two activity instances, the destroyed-and-leaked one plus the current one. If you just run the app and do nothing, you have not leaked the activity — while you have your own static reference to it, so does Android, because the activity is in the foreground and the user can see it.

    A leak does not itself cause a crash. It just means that you are tying up heap space that cannot be used for other things. Eventually, you will get an OutOfMemoryError on some allocation. If all you want to do is crash with an OutOfMemoryError, try allocating some massive byte[] (say, 1GB).

    If you specifically want to test an OutOfMemoryError triggered by leaked activities, you would need to: