Verify why object is only garbage collected on second garbage collection run

Viewed 353

For learning purposes I am currently experimenting with Garbage Collection (Pixel 2, Android 10 -> ART). My observation for the following experiment is that CountActivity is never deleted on the first explicit garbage collection run, but only at the second explicit garbage collection run. I want to understand why it isn't deleted on the first run. I assume it's due to some kind of generational garbage collection, but I want to verify it. How can I "watch" what the garbage collection does? E.g. is it possible to see the internal generations in which the garbage collection groups the heap?

My test example is as follows:

  1. MainActivity, which shows a Button "Show CountActivity"
  2. CountActivity which is shown when the Button "Show CountActivity" in the MainActivity is pressed.
  3. I end the CountActivity using my system back key

After this, I force an explicit garbage collection run, capture a heap. CountActivityis still there. After forcing a second explicit garbage collection run and again capturing a heap, the CountActivity is gone. Now I still want to understand why it takes two garbage collection runs.

MainActivity

class MainActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    setTheme(R.style.AppTheme)
    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_main)

    buttonStart.setOnClickListener {
      showCountActivity()
    }

  }

  private fun showCountActivity() {
    val intent = Intent(this, CountActivity::class.java)
    startActivity(intent)
  }

}

CountActivity

class CountActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_count)
  }
}
2 Answers

ART (the Android RunTime) uses what's referred to as a generational garbage collector, which basically creates buckets of objects based on when those objects were created (on the theory that newer objects are more likely to need to be garbage collected, since that's where all the temporary objects (builders, other intermediate objects, etc.) will end up. Thus, a garbage collection may not necessarily collect all generations of objects.

Here's some basic information about how Android's garbage collector works: https://developer.android.com/topic/performance/memory-overview

You can verify this by taking and analyzing a heap dump in Android Studio and noting that there is not a path to any GC roots from object in question.

Here's some very detailed documentation about the details of the GC (more likely to be useful to people working on ART itself, but still informative - the ergonomics section in particular discusses the generational collection a bit): https://source.android.com/devices/tech/dalvik/gc-debug

Analyzing the exact type and working of your garbage collection is best done by analyzing the logging it writes, which can automatically be analyzed by a free tool on the website gceasy.io that illustrate this in their blog:

https://blog.gceasy.io/2017/05/09/understanding-android-gc-logs/

If you would learn from the logs that you were to have a problem (e.g.: memory leak, performance issue) that you needed to further investigate you may want to create a heap dump and analyse all the objects and object references of your choosing with e.g. custom code:

https://rhye.org/post/advanced-android-heap-analysis/

Or maybe through a web application like ahat:

https://android.googlesource.com/platform/art/+/master/tools/ahat/README.txt

Related