Checker Leak   as of Julia version 2.4 (built on 23 Oct 2017)

belongs to group Basic

Identify situation where resources are leaked by preventing their garbage-collection


This checker identifies memory leaks in Android code. They occur every time an operating system managed object is stored into a data structure that might survive its life-cycle. As a consequence, the managed object will not be garbage-collected before the data structure. This results in increased memory consumption or application crash.

Action: Avoid the leak, by restructuring the logic of your code. Often, this requires simple operations such as making an inner class static. In other situations, the programmer can store in the data structure only the relevant information from the context, rather than the full context.

Examples


Consider the following three Android Activities:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class InnerClassActivity extends AppCompatActivity {

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

    Thread connectionThread = new Thread(new Runnable() {
      public void run ( ) {
        for (long l1 = 0L; l1 < 1000000000L; l1++)
          for (long l2 = 0L; l2 < 1000000000L; l2++)
            for (long l3 = 0L; l3 < 1000000000L; l3++) {}
      }
    });

    connectionThread.start();
  }
}
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

public class StaticFieldActivity extends AppCompatActivity {

  private static Wrapper wrapper;

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

    wrapper = new Wrapper();
    wrapper.setActivity(this);
  }

  private static class Wrapper {
    private Activity activity;

    public void setActivity(Activity activity) {
      this.activity = activity;
    }
  }
}
and
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

public class ServiceCallbackActivity extends AppCompatActivity {

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

    Connector connector = new Connector(this);
    connector.go();
  }
}
where the Connector class is defined as
import android.app.Service;
import android.content.Context;
import android.net.ConnectivityManager;

public class Connector implements ConnectivityManager.OnNetworkActiveListener {

  public Connector(Context context) {
    this.context = context;
  }

  public void go() {
    ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Service.CONNECTIVITY_SERVICE);
    manager.addDefaultNetworkActiveListener(this);
  }

  private Context context;

  @Override
  public void onNetworkActive() { ... }
}

This checker issues the following warnings:

Connector.java:7: [Leak: LeakThroughCallbackWarning] this callback implementation might leak an Android context wrapper
InnerClassActivity.java: [Leak: LeakThroughFieldWarning] this inner class seems to leak its outer object during the execution of method InnerClassActivity$1.run():void, that contains a loop
StaticFieldActivity.java: [Leak: LeakThroughFieldWarning] static field wrapper seems to leak a context wrapper

The first warning is generated since the listener attached to the ConnectivityManager points to a context which is actually an activity passed at line 11 of ServiceCallbackActivity. Since listeners become reachable from the observed object, which is a never garbage-collected system service, that activity gets leaked. The second warning is generated since the anonymous inner class that implements the Runnable is not static and consequently points to the outer object, which in this case is an activity. The latter will never be reclaimed while the potentially long computation inside the run() method is performed. Julia further observes that that computation contains a loop and is hence suspiciously long. The third warning is generated since static fields are never garbage-collected. Hence nothing reachable from a static field can be garbage-collected. In this case, the StaticFieldActivity is made reachable from the static field and hence gets leaked.

In InnerClassActivity a solution would be to define the Runnable as a separate, named class. That class can well be an inner class, as long as it is defined static:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class InnerClassActivity extends AppCompatActivity {

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

    Thread connectionThread = new Thread(new MyRunnable());
    connectionThread.start();
  }

  private static class MyRunnable implements Runnable {
    public void run ( ) {
      for (long l1 = 0L; l1 < 1000000000L; l1++)
        for (long l2 = 0L; l2 < 1000000000L; l2++)
          for (long l3 = 0L; l3 < 1000000000L; l3++) {}
    }
  }
}

In StaticFieldActivity a solution would be to detach the wrapped activity when it is destroyed and hence its life-cycle has finished:

import android.app.Activity;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

public class StaticFieldActivity extends AppCompatActivity {

  private static Wrapper wrapper;

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

    wrapper = new Wrapper();
    wrapper.setActivity(this);
  }

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

In ServiceCallbackActivity a solution would be to pass to the Connector only the information that it really needs. In this case, it does not really need to whole context, but only the ConnectivityManager:

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
 
public class ServiceCallbackActivity extends AppCompatActivity {
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_service_callback);
 
    Connector connector = new Connector((ConnectivityManager) getSystemService(Service.CONNECTIVITY_SERVICE));
    connector.go();
  }
}

where class Connector gets modified into:

import android.app.Service;
import android.content.Context;
import android.net.ConnectivityManager;
 
public class Connector implements ConnectivityManager.OnNetworkActiveListener {
 
  public Connector(ConnectivitytManager manager) {
    this.manager = manager;
  }
 
  public void go() {
    manager.addDefaultNetworkActiveListener(this);
  }
 
  private ConnectivityManager manager;
 
  @Override
  public void onNetworkActive() { ... }
}