Advanced Techniques for concurrency and memory management

Advanced Techniques for concurrency and memory management

Code samples:
Looper: https://gist.github.com/nhachicha/e993fe9b5f09f0595ccd
PhantomReference: https://gist.github.com/nhachicha/4ba780712a7ae179cc67
Synchronizers: https://gist.github.com/nhachicha/7596e912c81aae38d721

Managing memory & concurrency are one of the biggest challenges we face when it comes to writing testable & efficient apps.
based on the experience of building Realm, We'll dig into some patterns & tricks used to control memory footprint & multithreading.

Some of the topics this talks will cover are:

How to use the Java Reference API (WeakReference & ReferenceQueue) to work with the GC
Writing tests involving the GC
Working & testing code using the Handler/Looper api
Alternative asynchronous models (Messenger/ResultReceiver)
Threadpool customisation & pitfalls
Handler (mocking & testing, using IdleHandler)

8ec22ebc6cddbfbe097a580b8d685f75?s=128

Nabil Hachicha

October 29, 2015
Tweet

Transcript

  1. Advanced techniques for concurrency & memory management +NabilHachicha @nabil_hachicha Droidcon

    – October 2015
  2. Agenda • Item 1: GC & Reference API • Item

    2: Asynchronous communication • Item 3: Looper & Handler • Item 4: Synchronizers • Q/A
  3. Item 1: GC & Reference API

  4. WeakHashMap aka LeakMap for some Map<Object, String> map = new

    WeakHashMap<Object, String>(); Object key = new Object(); map.put(key, "xyz"); key = null;
  5. WeakHashMap class Key {} class Value { Key key; }

    Map<Key, Value> map = new WeakHashMap<Key, Value>(); Key key = new Key(); Value value = new Value(); map.put(key, value); // this will leak (value is a strong reference) value.key = key; leaking the key
  6. WeakHashMap defensive copy (aka copy constructor) com.google.android.gms.maps.model.Polygon polygon = ...;

    Map<List<LatLng>, Object> map = new WeakHashMap<List<LatLng>, Object>(); map.put(polygon.getPoints(), new Object());
  7. WeakHashMap literal String as Key WeakHashMap<String, Object> map = new

    WeakHashMap<String, Object>(); String key = "I'm pooled!"; map.put(key, new Object()); key = null;
  8. WeakHashMap Boxed type private static class IntegerCache { static final

    int low = -128; static final int high; static final Integer cache[]; Map<Integer, Object> map = new WeakHashMap<Integer, Object>(); for (int i=0; i < 128; i++) { map.put(i, new Object()); }
  9. java.lang.ref.Reference • Object pointing to another Object (referent) • Can

    be GC'ed anytime • Used to observe the lifecycle of an Object • Soft, Weak & Phantom for different Use Cases
  10. java.lang.ref.Reference Object referent = new Object (); Reference<Object> reference =

    new WeakReference<Object>(referent);
  11. java.lang.ref.Reference Object referent = new Object (); Reference<Object> reference =

    new WeakReference<Object>(referent); referent = null; Runtime.getRuntime().gc(); // Hint to run the GC
  12. java.lang.ref.Reference Object referent = new Object (); Reference<Object> reference =

    new WeakReference<Object>(referent); referent = null; Runtime.getRuntime().gc(); // Hint to run the GC if (reference.get() == null) { //The garbage collector deleted my referent } else { // referent object is still here }
  13. ReferenceQueue • BlockingQueue • GC place Reference to it when

    cleared
  14. ReferenceQueue ReferenceQueue<Object> queue = new ReferenceQueue<Object>(); for (int i =

    0 ; i < 100 ; i++) { WeakReference<Object> ref = new WeakReference<Object>(new Object(), queue); }
  15. ReferenceQueue ReferenceQueue<Object> queue = new ReferenceQueue<Object>(); for (int i =

    0 ; i < 100 ; i++) { WeakReference<Object> ref = new WeakReference<Object>(new Object(), queue); Reference<?> r; while ((r = queue.poll()) != null) { // polling to discover GC'ed referent // reference 'r' cleared } }
  16. ReferenceQueue Set<WeakReference<Object>> refs = new HashSet<WeakReference<Object>>(); ReferenceQueue<Object> queue = new

    ReferenceQueue<Object>(); for (int i = 0 ; i < 100 ; i++) { WeakReference<Object> ref = new WeakReference<Object>(new Object(), queue); refs.add(ref); Reference<?> r; while ((r = queue.poll()) != null) { // polling to discover GC'ed referent // reference 'r' cleared refs.remove(r); } }
  17. Finalizer • Can resurrect the original object • Require 2

    GC cycles • Slow, one thread performing finalisation
  18. PhantomReference • Always used with a ReferenceQueue • Signal that

    the Referent has been finalized • Referent is always null (prevent resurrection)
  19. PhantomReference Scenario: detect a memory leak

  20. class Activity { interface Listener {} Service service; Activity(Service service)

    { this.service = service; } void onStart() { service.registerListener(new Listener() {});//Listener hold a reference to Activity } void onStop () { service.unregisterListener(); service = null; } }
  21. class Service { Activity.Listener listener; void registerListener (Activity.Listener listener) {

    this.listener = listener; } void unregisterListener () { this.listener = null; } }
  22. Service service = new Service(); Activity activity = new Activity(service);

    activity.onStart();
  23. Service service = new Service(); Activity activity = new Activity(service);

    activity.onStart(); ReferenceQueue<Activity.Listener> referenceQueue = new ReferenceQueue<Activity.Listener>(); PhantomReference<Activity.Listener> reference = new PhantomReference<Activity.Listener>(service. listener, referenceQueue);
  24. Service service = new Service(); Activity activity = new Activity(service);

    activity.onStart(); ReferenceQueue<Activity.Listener> referenceQueue = new ReferenceQueue<Activity.Listener>(); PhantomReference<Activity.Listener> reference = new PhantomReference<Activity.Listener>(service. listener, referenceQueue); activity.onStop(); activity = null; // at this point we removed the strong reference to our Activity, service should not leak the Activity & Activity should be GC'd Runtime.getRuntime().gc();
  25. Service service = new Service(); Activity activity = new Activity(service);

    activity.onStart(); ReferenceQueue<Activity.Listener> referenceQueue = new ReferenceQueue<Activity.Listener>(); PhantomReference<Activity.Listener> reference = new PhantomReference<Activity.Listener>(service. listener, referenceQueue); activity.onStop(); activity = null; // at this point we removed the strong reference to our Activity, service should not leak the Activity & Activity should be GC'd Runtime.getRuntime().gc(); Reference<?> ref = referenceQueue.remove(TimeUnit.SECONDS.toMillis(10)); assertNotNull(ref); ref.clear();
  26. Write ~deterministic tests Involving GC • Used by AOSP FinalizationTester.java

    & LeakCanary lib • Prefer Runtime.getRuntime().gc(); https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/java/lang/ref/FinalizationTester.java https://github.com/square/leakcanary/blob/master/leakcanary-watcher/src/main/java/com/squareup/leakcanary/GcTrigger.java https://github.com/square/leakcanary/blob/master/leakcanary-watcher/src/main/java/com/squareup/leakcanary/RefWatcher.java
  27. IdentityHashMap • Regular Map

  28. IdentityHashMap • Regular Map • Reference-equality in place of object-equality

    for key & value • Faster if you have a complex hashCode it uses System.identityHashCode()
  29. IdentityHashMap • Regular Map • Reference-equality in place of object-equality

    for key & value • Faster if you have a complex hashCode it uses System.identityHashCode() • Support mutable key
  30. Item 2: Asynchronous models

  31. Activity - Service communication • Activity start service for background

    work • Service need to communicate results • Service may be in another process
  32. PendingIntent

  33. 1. Activity pass the PendingIntent as a Bundle argument (it's

    Parcelable)
  34. 1. Activity pass the PendingIntent as a Bundle argument (it's

    Parcelable) 2. Service invoke PendingIntent.send
  35. 1. Activity pass the PendingIntent as a Bundle argument (it's

    Parcelable) 2. Service invoke PendingIntent.send 3. onActivityResult will be called
  36. Messenger

  37. 1. Activity create a Handler

  38. 1. Activity create a Handler 2. create a messenger from

    a Handler new Messenger(handler)
  39. 1. Activity create a Handler 2. create a messenger from

    a Handler new Messenger(handler) 3. pass the Messenger as a Bundle argument
  40. 1. Activity create a Handler 2. create a messenger from

    a Handler new Messenger(handler) 3. pass the Messenger as a Bundle argument 4. Service invoke Messenger.send
  41. 1. Activity create a Handler 2. create a messenger from

    a Handler new Messenger(handler) 3. pass the Messenger as a Bundle argument 4. Service invoke Messenger.send 5. Handler will receive the Message
  42. ResultReceiver

  43. 1. Activity create a Handler

  44. 1. Activity create a Handler 2. create a ResultReceiver using

    the Handler new ResultReceiver(handler)
  45. 1. Activity create a Handler 2. create a ResultReceiver using

    the Handler new ResultReceiver(handler) 3. override onReceiveResult to receive msg
  46. 1. Activity create a Handler 2. create a ResultReceiver using

    the Handler new ResultReceiver(handler) 3. override onReceiveResult to receive msg 4. pass the ResultReceiver as a Bundle args
  47. 1. Activity create a Handler 2. create a ResultReceiver using

    the Handler new ResultReceiver(handler) 3. override onReceiveResult to receive msg 4. pass the ResultReceiver as a Bundle args 5. Service invoke ResultReceiver.send
  48. 1. Activity create a Handler 2. create a ResultReceiver using

    the Handler new ResultReceiver(handler) 3. override onReceiveResult to receive msg 4. pass the ResultReceiver as a Bundle args 5. Service invoke ResultReceiver.send 6. onReceiveResult will be called
  49. EventBus • Don't solve everything with EventBus • Global state

    • YoYo problem • No IPC https://en.wikipedia.org/wiki/Yo-yo_problem
  50. Item 3: Looper/Handler

  51. Test Runner • Test run inside an InstrumentationThread • InstrumentationThread

    prepare a Looper but doesn't start looping • Can't use it with Handler (Message will not be processed)
  52. Looper & Unit test Code examples full code here: https://gist.github.com/nhachicha/e993fe9b5f09f0595ccd

  53. Item 4: Synchronizers https://gist.github.com/nhachicha/7596e912c81aae38d721

  54. CountDownLatch • Initialize counter with # participants • await() sleep

    until counter = 0 • every thread call countDown()
  55. CyclicBarrier • Similar to CountDownLatch • await() sleep until all

    participants reaches the barrier
  56. Phaser • Thread register() to increment # of participants •

    Thread arrive() similar to countDown() • Barrier/Phase is crossed when Number(registered thread) = Number(arrived thread)
  57. Conclusion • Understand your problem, "your architecture is not the

    list of tools/lib" - Uncle Bob
  58. Conclusion • Understand your problem, "your architecture is not the

    list of tools/lib" - Uncle Bob • Understanding implementation & Time/Space complexity
  59. Conclusion • Understand your problem, "your architecture is not the

    list of tools/lib" - Uncle Bob • Understanding implementation & Time/Space complexity • Be opinionated don't follow trends
  60. We are hiring https://realm.io/jobs/ Q/A +NabilHachicha @nabil_hachicha