ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Garbage Collector, Memory Leaks, and Thread Termination in Android
    Mobile/Android 2020. 5. 26. 15:52

    All objects in your application must be garbage collected eventually except for objects that you explicitly want to use for the entire lifetime of the application. All started threads in your application must terminate in a timely manner to prevent memory leaks.

    1. Memory Allocation

    1.1 OutOfMemoryException

    If allocated memory never released, sooner or later the application will crash with OutOfMemoryException.

    2. Garbage Collector(GC)

    Garage Collector is a system process which automatically reclaims memory by discarding objects that are no longer in use. How does GC know which objects are no longer in use?

    2.1 Object Reachability

    2.2 Example

    Under normal conditions, Activity objects are reachable until after Android invokes their onDestroy() callback.

    2.3 Circular References Example

    If there are no other references to either of the objects, both are considered unreachable and will be eligible for GC. GC can handle circular references involving many objects.

    3. Memory Leaks

    Roots are objects which are always considered reachable, thus never cleaned by GC.

    3.1 Application Memory Graph

    Roots are the objects that basically hold other objects in memory. Roots are not reachable for garbage collection because roots aren't eligible for garbage collection. All other groups of objects which don't have any kind of references from roots, even if they have circle references inside the groups themselves, so these objects that aren't referenced from roots, there are non-reachable and therefore garbage collector will clean them.

    3.2 Important Roots in Android Application

    3.2.1 Objects referenced from static fields

    One of the most common sources for memory leaks in Android applications, or at least what used to be one of the most common sources in the past, are static references to activity objects. So basically, people will somehow reference activity from static fields and then those activities will be held in memory forever, even after the system called their own destroy method.

    3.2.2 Instances of Application class*

    Basically your custom subclass of the application class. And the rigorously speaking application subclasses are in proper Java roots. However, for all practical purposes, these objects behave like roots in the context of one single Android application. Therefore, the exact distinction between them and real roots isn't that important. So you just need to remember that if you reference something from the application class, then that object will be held in memory basically as long as you hold a reference to it in application class.

    3.2.3 Live threads

    From the moment a thread is started and until its run() method returns, all objects referenced by the thread are reachable. 

    Instances of anonymous and inner classes have implicit references to enclosing class objects.

    Case 1. Anonymous Threads

    MainActivity instance can be garbage collected as long as the anonymous Thread instance is live. So for as long as this Thread takes to execute, garbage collector won't be able to basically claim the memory associated with MainActivity. And if this Thread runs forever, well, then MainActivity will be leaked forever. 

    Case 2. Inner Threads

    if you have like inner thread and we instantiate and start it, this Thread will, again, have an implicit reference to the enclosing instance of MainActivity. And as long as this thread is alive, garbage collector won't be able to clean the memory associated with the enclosing MainActivity even if that activity is not in use anymore. For instance, its own destroy method has already been called by the system and another activity is being recreated instead of, for example, if you rotate your phone, as long as this thread is alive, then all instance of MainActivity will hang around, and if the run method of this Thread never returns, then basically I would just leak my MainActivity forever.

    4. Thread Termination

    4.1 Return from run() after successful execution

    Let's say we've got this main activity that instantiates a new anonymous thread, and inside this thread, we basically do two things. One, fetch some items from the endpoint. And then, after we get these items, we want to insert them into our local database. So, two actions and the reason why we want to do that on a stand-alone thread is that both of these actions can take a considerable amount of time. And we wouldn't like to let these actions interfere with other processing in our application, so we start a new thread. However long these two actions will take, after both of them complete, the run method will return naturally and this anonymous thread will be dead afterward. One interesting thing to note here is that inside this anonymous thread, I'm using fields of the enclosing main activity. And that's only possible because this anonymous thread has an implicit reference to the enclosing object. In this case, that might not be a problem because these actions will take five, 10 seconds, 15 seconds, and then this thread will be dead and the activity will become eligible for garbage collection. But that's not always the case. Sometimes you might have some process inside your thread that can actually take a lot of time or maybe it will never complete. In these cases, the thread will basically hold on to this activity indefinitely and it might cause a memory leak.

    4.2 Return from run() in response to internal error

    4.3 Return from run() in response to externally set flag

    Inside the run method, after the fetch is completed, we'll check the value of this flag and if this flag is true, again, short-circuit and return from run method without inserting items into the database. In other words, if any entity in our application will change the value of this flag to true, then this thread will short-circuit and return from its run method without executing all the code. One very interesting and tricky thing to note here is that if this flag is changed after the thread passes this if check, then this will have no effect whatsoever. So, you need to keep in mind that changing the value of this flag doesn't necessarily mean that the operation will be aborted. It might be the case that you change the value of this flag and the operation still succeeds.

    4.4 Return from run() in response to interruption

    It will basically need to grab this reference and call interrupt on it. Inside the run method I check the value returned from Thread.interrupted static method and if the value is true, then I short-circuit the execution and return from run method. And know that this code inside run method is very similar to what we had before with externally set flag. And indeed, in some sense, externally set flag and thread interruption are two similar mechanisms. However, thread interruption, in practice, is a much more nuanced and complex mechanism. And therefore, I recommend that you always favor externally set flag over thread interruption. And the reason why I recommend that, is because the thread interruption is indeed a very nuanced mechanism. You need to understand that in great detail to use it and not make a mistake.

    5. Reference

    https://medium.com/@techyourchance

    'Mobile > Android' 카테고리의 다른 글

    Android Architecture  (0) 2020.06.02
    UI Thread  (0) 2020.05.26
    Android Processes and Threads  (0) 2020.05.26

    댓글

Designed by Tistory.