Slide 1

Slide 1 text

@pacoworks About Memory Management in Fully Reactive Apps Paco Estevez - 2017 1

Slide 2

Slide 2 text

@pacoworks A quick recap of JVM memory management Equivalences between classes and lambdas Identifying leaks in reactive apps Designing solutions that respect memory <—— Quiz here! 2

Slide 3

Slide 3 text

@pacoworks How memory is handled by the JVM 3

Slide 4

Slide 4 text

@pacoworks TLDR: Mark & Sweep Objects have relations to each other: they have explicit fields, or they’re implicitly captured and referenced from an outer scope When an object doesn’t have any pointer to it it’s considered to be ready for collection Garbage Collection algorithm marks objects ready for collection and then sweeps them from memory It’s not deterministic but it’s reliable 4

Slide 5

Slide 5 text

@pacoworks GC Roots - the unsweepables Some objects are marked as Garbage Collection Roots, an indication that they will not be considered for collection All objects referenced by roots transitively won’t be collected either Most common roots: Locals and parameters in stack frames Static fields Threads spawned by the app 5

Slide 6

Slide 6 text

@pacoworks Leaks in Android Activities are god objects that contain large chunks of memory related to UI and services Activities cannot be constructed and have to be manually released Activities act as a glorified callback for: Activity manager Permissions Intent requests to other parts of the system They are also a service locator and give access to IO 6

Slide 7

Slide 7 text

@pacoworks “Most leaks in Android are caused by inappropriately retaining a callback called Activity.” – Maybe Paco 7

Slide 8

Slide 8 text

@pacoworks Common leak causes - Hidden dependency graphs class MyApp extends Application { DBService dbService; } class MyActivity extends Activity implements DBListener { @Inject DBService dbService; public void onCreate() { dbService.watchChanges("users", this); } @Override public void onChange(Object user) { ... } } Application keeps DBService alive Activity is set as a DBService callback which is never disconnected Transitive ownership assures that Activity doesn’t get collected 8

Slide 9

Slide 9 text

@pacoworks Common leak causes - Anonymous class captures class MyActivity { public void onCreate() { NetworkService.getInstance() .request(user -> updateProfile(user)); ) } void updateProfile(User user) { ... } } updateProfile is a method of the MyActivity class The compiler adds an implicit reference to the MyActivity.this object in the lambda NetworkService holds the callback inside a Singleton static 9

Slide 10

Slide 10 text

@pacoworks Common leak causes - Inner classes class MyActivity { public void onCreate() { new Thread(new LongOp()).start(); } void showDetails(Result result) { ... } private class LongOp extends Runnable { void run() { Result result = longOp(); showDetails(result); } } } The compiler adds an implicit reference to MyActivity.this to access showDetails RecyclerView holds the Adapter, which holds the Activity, which holds the RecyclerView… Leaks in a Thread 10

Slide 11

Slide 11 text

@pacoworks Fixing leaks in OOP class MyActivity implements Callback { private LongOp op; public void onCreate() { op = new LongOp(this); new Thread(op).start(); } public void onDestroy() { if (op != null) { op.release(); } } void call(Data data) { showDetails(data); } private showDetails() { ... } } class LongOp extends Runnable { private Callback callback; class LongOp(Callback callback) { this.callback = callback; } void release() { callback = null; } void run() { if (callback != null) { callback.call(longOp()); } } interface Callback { void call(Data data); } } 11

Slide 12

Slide 12 text

@pacoworks Fixing leaks in OOP Mutable fields or box references i.e. WeakReference Manual management of attach-detach null checks Not thread safe without effort No real business value 12

Slide 13

Slide 13 text

@pacoworks A touch of functional programming 13

Slide 14

Slide 14 text

@pacoworks Solving transitive ownership: where to store dependencies? 14

Slide 15

Slide 15 text

@pacoworks Open class… class RequestUseCase { private final INetworkService network; private final IDataBase db; public RequestUseCase(INetworkService network, IDataBase db) { this.network = network; this.db = db; } Observable operation(String id) { return Observable.just(id) .flatMap(id -> network.request(id)) .flatMap(result -> db.store(result)); } } All business logic Retained fields that are only transitive dependencies 15

Slide 16

Slide 16 text

@pacoworks …or closures? static Observable operation( String id, final INetworkService network, final IDataBase db) { return Observable.just(id) .flatMap(id -> network.request(id)) .flatMap(result -> db.store(result)); } Lambdas capture the dependencies Doesn’t require to write an entire class Dependencies are retained only for as long as the Observable is alive 16

Slide 17

Slide 17 text

@pacoworks Open class or closures? class MyActivity { @Inject INetworkService network; @Inject IDataBase db; private MyUseCase useCase; public void onCreate() { /* Creates a transitive ownership node */ useCase = new MyUseCase(network, db); } public void onResume() { useCase.operation("1") .subscribe(result -> displayResult(result)); } } class MyActivity { @Inject INetworkService network; @Inject IDataBase db; public void onResume() { /* No new ownerships */ operation("1", network, db) .subscribe(result -> displayResult(result)); } } 17

Slide 18

Slide 18 text

@pacoworks Properties of closures Fancy name for capturing local dependencies in lambdas & single-method anonymous classes Can be used as a poor man’s class on the spot Values are captured as implicit final fields Retained values are encapsulated privately Reusable through functional patterns 18

Slide 19

Slide 19 text

@pacoworks Reusing functions - Partial Application Function> applied = apply(network, db, id -> operation(network, db, id), /* These values get pre-filled */ network, db); Observable.merge( applied.call("1"), applied.call("2"), applied.call("fancy") ).subscribe(result -> show(result)); Creates a partially applied function object during runtime Captures some dependencies inside a closure that calls the method passed Like RequestUseCase but as a reusable pattern 19

Slide 20

Slide 20 text

@pacoworks But Classes are supposed to encapsulate state and behaviour By externalising the state and services to be injected for testing purposes you have already broken this principle What if my class encapsulates multiple behaviours for the same fields? The fields are the state, your methods are your operations. If you extract the state and pass it to each function explicitly you should get the same result 20

Slide 21

Slide 21 text

@pacoworks Singletons are passé 21

Slide 22

Slide 22 text

@pacoworks Perfect functional world Dependencies should be injected as instances of a function, plain data, or an Observable State is external and separate to behaviour, services are pure functions Scope dependencies and avoid Singletons simple fields in Activity and Application with a module like in Dagger 22

Slide 23

Slide 23 text

@pacoworks Injecting static methods Observable operation() { NetworkService.request() .doOnError(e -> RxLogger.logError(e)); } /* Pass globals as functions */ Observable operation( Callable> network, Consumer logger) { return network.call() .doOnError(logger); } ... /* Call it with a function object */ operation( () -> NetworkService.request(), e -> RxLogger.logError(e)); Solves dependencies on static methods Decouples so no testing libraries like PowerMock are required Doesn’t solve any memory management issues Interfaces may be different in RxJava1 and Kotlin 23

Slide 24

Slide 24 text

@pacoworks Wrapping Singletons static Observable wrap( ConnectivityService service) { return Observable.create(emitter -> { final Callback callback = /* Forward value from callback */ isConnected -> emitter.onNext(isConnected); /* Remove callback when the Observable terminates */ emitter.setCancellable(() -> service.removeCb(callback)); /* Start listening */ service.addCb(callback); }); } operation(() -> wrap(ConnectivityService.getInstance(activity)) ); Matches the lifecycle of an Observable Fixes memory management problems, but it also hides them Wraps the Singleton in a closure Decouples from ConnectivityService to test without using libraries like PowerMock 24

Slide 25

Slide 25 text

@pacoworks Threading challenges using RxJava 25

Slide 26

Slide 26 text

@pacoworks Those pesky Threads Transitive dependencies and Statics are mostly design problems that can be fixed before they touch application logic Threads are a runtime behaviour problem that is framework and paradigm agnostic in the JVM Our UI framework is properly single-threaded Observable as our abstraction for concurrency 26

Slide 27

Slide 27 text

@pacoworks Dissecting Observable creation An Observable receives an Observer, which contains a function to execute upon subscription Each operation like map() or scan() creates its own nested Observer Operations like subscribeOn() schedule thread changes inside closures parent.setDisposable( scheduler.scheduleDirect( () -> source .subscribe(parent) )); ———> ———> 27

Slide 28

Slide 28 text

@pacoworks Dissecting Observable unsubscription Sequentially destroys all nested Observers in the chain Dispose is called in each one of them to set them to null Garbage Collection gets rid of them eventually Scheduled operations are still running but their results are not sent forward /** * Atomically disposes the Disposable in the field if not already disposed. * @param field the target field * @return true if the current thread managed to dispose the Disposable */ public static boolean dispose(AtomicReference field) { Disposable current = field.get(); Disposable d = DISPOSED; if (current != d) { current = field.getAndSet(d); if (current != d) { if (current != null) { current.dispose(); } return true; } } return false; } 28

Slide 29

Slide 29 text

@pacoworks The Observable memory lifecycle Gets created with a bunch of functions nested in Observers Runs synchronously or asynchronously depending on the operations defined by the Observers Upon completion or cancellation it nulls all the nested references inside, including threaded ones 29

Slide 30

Slide 30 text

@pacoworks Quiz for the audience! What keeps these Observables alive? 30

Slide 31

Slide 31 text

@pacoworks Quiz 1 public void onCreate() { Observable.just(1) .subscribe(num -> show(num)); } 31

Slide 32

Slide 32 text

@pacoworks Quiz 1 public void onCreate() { Observable.just(1) .subscribe(num -> show(num)); } onCreate() stack frame 32

Slide 33

Slide 33 text

@pacoworks Quiz 2 public void onCreate() { RxView.clicks(label).subscribe(...); } 33

Slide 34

Slide 34 text

@pacoworks Quiz 2 public void onCreate() { RxView.clicks(label).subscribe(...); } label.onClickListener() 34

Slide 35

Slide 35 text

@pacoworks Quiz 3 public class StateHolder { PublishSubject myStrings = PublishSubject.create(); } 35

Slide 36

Slide 36 text

@pacoworks Quiz 3 public class StateHolder { PublishSubject myStrings = PublishSubject.create(); } whatever retains StateHolder 36

Slide 37

Slide 37 text

@pacoworks Quiz 4 public void onCreate() { Observable.interval( 1, TimeUnit.SECOND, Schedulers.computation()) .subscribe(num -> show(num)); } 37

Slide 38

Slide 38 text

@pacoworks Quiz 4 public void onCreate() { Observable.interval( 1, TimeUnit.SECOND, Schedulers.computation()) .subscribe(num -> show(num)); } thread in computation 38

Slide 39

Slide 39 text

@pacoworks Quiz 4 public void onCreate() { Observable.interval( 1, TimeUnit.SECOND, Schedulers.computation()) .subscribe(num -> show(num)); } thread in computation LOOKS LIKE A POTENTIAL LEAK TO ME 39

Slide 40

Slide 40 text

@pacoworks Terminating Observables “Natural causes” Retain a Subscription and cancel it manually Using one of the operators like takeUntil() 40

Slide 41

Slide 41 text

@pacoworks Cancelling a Subscriber Simple solution, not far from the OOP way For most cases it plays nice with the Activity lifecycle You have twenty lovely MV* libraries with variations of same idea 41

Slide 42

Slide 42 text

@pacoworks The operator takeUntil() Allows for fine-grained lifecycle control Needs an Observable signal, and cancels the Observable once the first value is received Works with the Android lifecycle with some adjustments 42

Slide 43

Slide 43 text

@pacoworks Reactive Activity lifecycle BehaviorRelay lifecycle = BehaviorRelay.create() protected void onCreate(Bundle b) { if (b == null) { lifecycle.call(ENTER); } lifecycle.call(CREATE); } protected void onDestroy() { lifecycle.call(DESTROY); if (isFinishing()) { lifecycle.call(EXIT); } } Map every lifecycle state to a piece of data Store the data in an Observable that caches the latest value, like BehaviourSubject or BehaviourRelay 43

Slide 44

Slide 44 text

@pacoworks Fixing threads with takeUntil() public void onCreate() { lifecycle .filter(is(CREATE)) .switchMap(enter -> Observable.interval( 1, TimeUnit.SECOND, computation())) .observeOn(mainThread()) .takeUntil(lifecycle.filter(is(DESTROY))) .subscribe(num ->show(num)) } Start operation only if state is CREATE Cancel all previous intervals with switchMap() Stop the interval and listening to the lifecycle if state is DESTROY 44

Slide 45

Slide 45 text

@pacoworks Fixing Singletons with takeUntil() operation(() -> wrap(ConnectivityService.getInstance(activity)) .takeUntil(lifecycle.filter(is(DESTROY))) ); Ties the lifecycle of the Observable to the Activity Only happens on the Android layer Transparent for the Singleton and the operation Allows chains where only some Observables are tied to Activities 45

Slide 46

Slide 46 text

@pacoworks Designing Fully Reactive Apps 46

Slide 47

Slide 47 text

@pacoworks High level overview A Business Logic layer where data is transformed according to an specification A View layer as a way to interact with the user A Services layer to interact with the system 47

Slide 48

Slide 48 text

@pacoworks High level overview A Business Logic layer where data is transformed according to an specification A View layer as a way to interact with the user A Services layer to interact with the system JUST FOCUSING ON MEMORY TODAY 48

Slide 49

Slide 49 text

@pacoworks The passive View Reactive world mandates a purely passive view A View can have two operations: output and input 49

Slide 50

Slide 50 text

@pacoworks Not the passive View Reactive world mandates a purely passive view A View can have two operations: output and input 50

Slide 51

Slide 51 text

@pacoworks Some passive Views // DragAndDropView void updateElements(List elements); void updateSelected(Set selected); // RotationViewInput void showLoading(String message); void showError(String reason); void showWaiting(int seconds); void showRepositories(List repos); Simple data formatted to be displayed on screen “I don’t care how you display it” Note these are not part of an interface 51

Slide 52

Slide 52 text

@pacoworks The best passive View Reactive world mandates a purely passive view A View has one operation: display data Any reference to it is a potential cause of leaks, as it will always come with a reference to any UI widgets captured 52

Slide 53

Slide 53 text

@pacoworks The View-Service duality A View layer as a way to interact with the user A Services layer to interact with the system 53

Slide 54

Slide 54 text

@pacoworks The View-Service duality A View layer as a way to display information A Services layer to interact with the user and the system 54

Slide 55

Slide 55 text

@pacoworks Some Services // DragAndDropUserInteraction Observable> dragAndDropMoves(); Observable> listClicks(); // DatabaseService Single> queryEntriesBefore(Date date); // ActivityView Observable lifecycle(); // NetworkService Single request(String id); // StateHolder Observable updates(); A service is a collection of functions returning a version of Observable Decoupled from space, time, and error handling Data received is immutable and never null Signatures do not know of Android framework code Note these are not part of an interface 55

Slide 56

Slide 56 text

@pacoworks Problems with Services They are bound to the part of the code they talk to: Hold a Context for IO Bound to the Activity lifecycle Hold a reference to a widget Opinionated 3rd party libraries 56

Slide 57

Slide 57 text

@pacoworks Problems with Services They are bound to the part of the code they talk to: Hold a Context for IO Bound to the Activity lifecycle Hold a reference to a widget Opinionated 3rd party libraries <—— Wrap or replace 57

Slide 58

Slide 58 text

@pacoworks Fixing Activity problems in Services wrap(ConnectivityService.getInstance(activity)) .takeUntil(lifecycle.filter(is(DESTROY))) Hold a Context for IO Bound to the Activity lifecycle 58

Slide 59

Slide 59 text

@pacoworks Fixing widget problems in view Services @Bind(R.id.my_bview) View label; Observable viewClicks = null; void onCreate() { viewClicks = RxView.clicks(label); } @Override Observable viewClicks() { // Leak! The Observable retains // a direct reference to the label return viewClicks; } VIEW RXVIEW BUSINESS LOGIC 59

Slide 60

Slide 60 text

@pacoworks Fixing widget problems in view Services PublishRelay clicksRelay = PublishRelay.create(); void onCreate() { // Not a leak! The Observable lives in // the view’s onClickListener and // is collected alongside the view RxView.clicks(label) .subscribe(clicksRelay); } Observable viewClicks() { // Only the Relay is passed along return clicksRelay.asObservable(); } VIEW RXVIEW BUSINESS LOGIC PUBLISH 60

Slide 61

Slide 61 text

@pacoworks Real problems with Services Identify which ones are leaky and which ones aren’t Leaky can only run while the Activity is alive Not leaky can run until required or completion 61

Slide 62

Slide 62 text

@pacoworks All together now! 62

Slide 63

Slide 63 text

@pacoworks Our business logic function static void subscribePagination( PaginationState state, Observable endOfPage, Function>> service) { // Chain with switchMap doSM( // For every new state () -> state.elements, // Wait for the end of the page elements -> endOfPage.first(), // Get latest page elements, click -> state.pages.first(), // Request and append new page elements, click, page -> service.apply(page) .map { elements.plus(it) } .doOnNext { state.pages.call(page + 1) } ) // Reapply state .subscribe(state.elements) } Coupled only with functions, observables and data Single responsibility No leaky direct interaction with UI No UI thread required 63

Slide 64

Slide 64 text

@pacoworks Subscribing our business logic class PaginationScreen { PaginationState state = new PaginationState(); PublishRelay endOfPagePRelay = PublishRelay.create(); @Inject NetworkService network; public PaginationScreen() { /* Run only once per creation */ subscribePagination( state, endOfPagePRelay, page -> network.requestMore(page)); } public void onCreate() { /* Android code to forward end of page to relay */ } } Apply values to state rather than directly to UI Relay to proxy and avoid passing the view NetworkService is passed as a function 64

Slide 65

Slide 65 text

@pacoworks Abstracting binding State to UI void bind(Observable lifecycle, Scheduler mainThreadScheduler, Observable state, Consumer viewAction) { lifecycle .filter(is(CREATE)) .switchMap(state) .observeOn(mainThreadScheduler) .takeUntil( lifecycle .filter(is(DESTROY))) .subscribe(viewAction) } Fixes leaky UI! Bind with lifecycle assurance Add main thread too Observable subscribed with Consumer 65

Slide 66

Slide 66 text

@pacoworks Binding our UI class PaginationScreen { BehaviorRelay lifecycle = BehaviorRelay.create(); PaginationState state = new PaginationState(); public PaginationScreen() { /* Subscription code */ } public void onCreate() { /* Android code to initialize UI and forward to relays */ /* Bind state once the UI is ready */ bind( lifecycle, mainThread(), state.elements, elements -> updateList(elements)); } void updateList(List elements) { ... } } Our UI dependency is passed as a function Binding only after UI is ready 66

Slide 67

Slide 67 text

@pacoworks Recap 67

Slide 68

Slide 68 text

@pacoworks Fully Reactive Apps Subscriptions to business logic will run until origin stored in PaginationState disappears 68

Slide 69

Slide 69 text

@pacoworks Quiz 3 - Reminder public class PaginationState { BehaviorRelay elements = BehaviorRelay.create(); BehaviorRelay pages = BehaviorRelay.create(); } screen retains PaginationState 69

Slide 70

Slide 70 text

@pacoworks Fully Reactive Apps Subscriptions to business logic will run until origin stored in PaginationState disappears That means that they’ll survive across rotation as long as the state is retained too! 70

Slide 71

Slide 71 text

@pacoworks Fully Reactive Apps Subscriptions to business logic will run until origin stored in PaginationState disappears That means that they’ll survive across rotation as long as the state is retained too! Lifecycle-bound services are transparent 71

Slide 72

Slide 72 text

@pacoworks Fully Reactive Apps Subscriptions to business logic will run until origin stored in PaginationState disappears That means that they’ll survive across rotation as long as the state is retained too! Lifecycle-bound services are transparent Bind with UI runs between CREATE and DESTROY and will be called again onCreate() 72

Slide 73

Slide 73 text

@pacoworks Fully Reactive Apps: http://www.pacoworks.com/ 2016/11/02/fully-reactive-apps-at- droidcon-uk-2016-2/ Modern Garbage Collection: https://blog.plan99.net/modern- garbage-collection-911ef4f8bd8e Sample project: https://github.com/pakoito/ FunctionalAndroidReference pacoworks.com @pacoworks github.com/pakoito Slides: http://tinyurl.com/RxMemBytes17 This presentation will be soon available on the Droidcon Bytes London website at the following link: https://bit.ly/2kS4Pyn 73