Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Advanced Techniques for concurrency and memory ...

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)

Nabil Hachicha

October 29, 2015
Tweet

More Decks by Nabil Hachicha

Other Decks in Programming

Transcript

  1. Agenda • Item 1: GC & Reference API • Item

    2: Asynchronous communication • Item 3: Looper & Handler • Item 4: Synchronizers • Q/A
  2. WeakHashMap aka LeakMap for some Map<Object, String> map = new

    WeakHashMap<Object, String>(); Object key = new Object(); map.put(key, "xyz"); key = null;
  3. 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
  4. 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());
  5. 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;
  6. 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()); }
  7. 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
  8. 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
  9. 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 }
  10. ReferenceQueue ReferenceQueue<Object> queue = new ReferenceQueue<Object>(); for (int i =

    0 ; i < 100 ; i++) { WeakReference<Object> ref = new WeakReference<Object>(new Object(), queue); }
  11. 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 } }
  12. 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); } }
  13. Finalizer • Can resurrect the original object • Require 2

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

    the Referent has been finalized • Referent is always null (prevent resurrection)
  15. 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; } }
  16. class Service { Activity.Listener listener; void registerListener (Activity.Listener listener) {

    this.listener = listener; } void unregisterListener () { this.listener = null; } }
  17. 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);
  18. 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();
  19. 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();
  20. 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
  21. IdentityHashMap • Regular Map • Reference-equality in place of object-equality

    for key & value • Faster if you have a complex hashCode it uses System.identityHashCode()
  22. 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
  23. Activity - Service communication • Activity start service for background

    work • Service need to communicate results • Service may be in another process
  24. 1. Activity pass the PendingIntent as a Bundle argument (it's

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

    Parcelable) 2. Service invoke PendingIntent.send 3. onActivityResult will be called
  26. 1. Activity create a Handler 2. create a messenger from

    a Handler new Messenger(handler) 3. pass the Messenger as a Bundle argument
  27. 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
  28. 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
  29. 1. Activity create a Handler 2. create a ResultReceiver using

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

    the Handler new ResultReceiver(handler) 3. override onReceiveResult to receive msg
  31. 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
  32. 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
  33. 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
  34. EventBus • Don't solve everything with EventBus • Global state

    • YoYo problem • No IPC https://en.wikipedia.org/wiki/Yo-yo_problem
  35. 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)
  36. CountDownLatch • Initialize counter with # participants • await() sleep

    until counter = 0 • every thread call countDown()
  37. Phaser • Thread register() to increment # of participants •

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

    list of tools/lib" - Uncle Bob • Understanding implementation & Time/Space complexity
  39. 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