Performance #1 memory

Preview:

Citation preview

A good memory is one trained to forget the trivial

Where is my RAM, dude?

+

First,

Yonatan LevinGoogle Developer

Expert & Android @ Gett

Idan FelixSenior Android &

Redhead Varonis

Jonathan Yarkoni

Android Developer & Advocate Ironsource

Android Academy Staff

Britt Barak

Android LeadReal

Muiriel Felix

Android Design

Logistics

https://www.facebook.com/groups/android.academy.ils/

#PerfMatters

What’s next?

30/5 - Felix

- How to draw right? ( Overdraw, Cliprect, Bitmaps)

13/6 - Britt

- View, Animations

What’s next?

4/7 - Yonatan

- Networking, JSON, Batching, Location

10/8 - Felix

- Battery & CPU

What’s next?

14/9 - Britt

- Threadinggg...

And…

31.10New course coming

Memory mmm…

OutOfMemory is just an iceberg of problems with a memory

Wow! What should I do?

1.Be Aware2.Learn & Understand3.Apply - Profile your code

I’mCollector,

Garbage Collector

GC

Goal

1.Find objects that can’t be accessed

2.Reclaim the resource from them

Mark & Sweeping

So what happens when we don’t have enough free space

for our “new Object();”?

Larger heap

Pitfall

More Garbage = Larger GC Pauses

Dalvik07-01 15:56:16.785: I/dalvikvm-heap(30615): Grow heap (frag case) to 38.179MB for 8294416-byte allocation

07-01 15:56:17.625: I/Choreographer(30615): Skipped 35 frames! The application may be doing too much work on its main thread.

07-01 15:56:19.035: D/dalvikvm(30615): GC_CONCURRENT freed 35838K, 43% free 51351K/89052K, paused 3ms+5ms, total 106ms

07-01 15:56:19.035: D/dalvikvm(30615): WAIT_FOR_CONCURRENT_GC blocked 96ms

GC ReasonGC_CONCURRENT - A concurrent GC that frees up memory as your heap begins to fill up.

GC_FOR_MALLOC - A GC caused because your app attempted to allocate memory when your heap was already full, so the system had to stop your app and reclaim memory.

GC_HPROF_DUMP_HEAP - A GC that occurs when you request to create an HPROF file to analyze your heap.

GC_EXPLICIT - An explicit GC, such as when you call gc() (which you should avoid calling and instead trust the GC to run when needed).

GC_EXTERNAL_ALLOC - This happens only on API level 10 and lower (newer versions allocate everything in the Dalvik heap). A GC for externally allocated memory (such as the pixel data stored in native memory or NIO byte buffers).

ART07-01 16:00:44.531: I/art(198): Explicit concurrent mark sweep GC freed 700(30KB) AllocSpace objects, 0(0B) LOS objects, 792% free, 18MB/21MB, paused 186us total 12.763ms

07-01 16:00:46.517: I/art(29197): Background partial concurrent mark sweep GC freed 74626(3MB) AllocSpace objects, 39(4MB) LOS objects, 1496% free, 25MB/32MB, paused 4.422ms total 1.371747s

07-01 16:00:48.534: I/Choreographer(29197): Skipped 30 frames! The application may be doing too much work on its main thread.

07-01 16:00:48.566: I/art(29197): Background sticky concurrent mark sweep GC freed 70319(3MB) AllocSpace objects, 59(5MB) LOS objects, 825% free, 49MB/56MB, paused 6.139ms total 52.868ms

GC ReasonConcurrent - A concurrent GC which does not suspend app threads. This GC runs in a background thread and does not prevent allocations.

Alloc - The GC was initiated because your app attempted to allocate memory when your heap was already full. In this case, the garbage collection occurred in the allocating thread.

Explicit - The garbage collection was explicitly requested by an app, for instance, by calling gc() or gc().

NativeAlloc - The collection was caused by native memory pressure from native allocations such as Bitmaps or RenderScript allocation objects

Why i care about memory

60FPS!!!

But How Does It Work?

But How Does It Work?

SmoothMotion

60

No Difference

60+

Flip Book

12

Movies

Frames Per Second

Fluid Motion

24+effects

We Have A Winner!

SmoothMotion

60

No Difference

60+

Flip Book

12

Movies

Frames Per Second

Fluid Motion

24+effects

SmoothMotion

60

60 FPS

60 Frames / Second = Frame / 16.666 Millisecond

Okey, OkeyI got the idea.What’s next?

Reasons

1.A lot of allocations

2.Memory leaks

3.Not designing for performance

Memory churnpublic void startAllocations(View view) {

AndroidAcademy object;

for (int i = 0; i < 1000; i++) {

object = new AndroidAcademy();

}

}

A Volume hit the GC

When usually avoid it happen

1.onDraw()

2.String concatenation on buffer reading from stream

3.Inner loops allocation

Good Practice

1.Allocate outside of the loop

2.Consider reuse of the object

3.Pool of objects

4.To think does it really necessary?

Memory Leakpublic class MainActivity extends AppCompatActivity {

private static Drawable sBackground;

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView textView = (TextView) findViewById(R.id.text); textView.setText("Leak fun!");

if (sBackground == null) { sBackground = ContextCompat.getDrawable(this,android.R.drawable.ic_menu_zoom); } textView.setBackgroundDrawable(sBackground); }

}

Detecting Memory Issues

GC now!Dump Java

Heap

Start Allocation Tracker

LeakCanary

How to?public class GetTaxiDriverBoxApp extends Application { protected RefWatcher mRefWatcher;

public void onCreate() { super.onCreate(); mRefWatcher = installLeakCanary(); }

protected RefWatcher installLeakCanary() { return RefWatcher.DISABLED;}

}

In Debug Onlypublic class DebugApplication extends GetTaxiDriverBoxApp {

@Override protected RefWatcher installLeakCanary() { mRefWatcher = LeakCanary.install(this, LeakSlackUploadService.class, AndroidExcludedRefs.createAppDefaults().build()); return mRefWatcher; }}

When should be killed - watched it@Overridepublic void onDetach() { super.onDetach();

GetTaxiDriverBoxApp.getRefWatcher(getContext()).watch(this);

}

Let’s dive into some examples

Memory Leak Life Example Google Maps* GC ROOT thread java.lang.Thread.<Java Local> (named 'AsyncTask #5')* references android.os.AsyncTask$3.this$0 (anonymous class extends java.util.concurrent.FutureTask)* references com.google.maps.android.clustering.ClusterManager$ClusterTask.this$0* references com.google.maps.android.clustering.ClusterManager.mRenderer* references com.gettaxi.dbx_lib.features.heatmap.FixedBucketsResizingDrawableClusterRenderer.mIconGenerator* references com.google.maps.android.ui.IconGenerator.mContext* leaks com.gettaxi.dbx.android.activities.MainActivity instance

Memory Leak Life Example

Some of them are really hard to spot

Memory Leak - Example* GC ROOT thread com.squareup.picasso.Dispatcher.DispatcherThread.<Java Local>* references android.os.Message.obj* references com.example.MyActivity$MyDialogClickListener.this$0* leaks com.example.MyActivity.MainActivity instance

Handler.javapublic final Message obtainMessage(int what, Object obj) { return Message.obtain(this, what, obj);}

public void handleMessage(Message msg) { switch (msg.what) { case WHAT_ORDER_UPDATES: { //do something break; }}

Simple Dialognew AlertDialog.Builder(this) .setPositiveButton("Baguette", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { MyActivity.this.makeBread(); } }).show();

.class of OnClickListenerclass MyActivity$0 implements DialogInterface.OnClickListener { final MyActivity this$0; MyActivity$0(MyActivity this$0) { this.this$0 = this$0; } @Override public void onClick(DialogInterface dialog, int which) { this$0.makeBread(); }}

new AlertDialog.Builder(this) .setPositiveButton("Baguette", new MyActivity$0(this)); .show();

AlertController.javapublic void setButton(int whichButton, CharSequence text, DialogInterface.OnClickListener listener, Message msg) { if (msg == null && listener != null) { msg = mHandler.obtainMessage(whichButton, listener); } switch (whichButton) { case DialogInterface.BUTTON_POSITIVE: mButtonPositiveText = text; mButtonPositiveMessage = msg; break; //...

private final View.OnClickListener mButtonHandler = new View.OnClickListener() { @Override public void onClick(View v) { final Message m; if (v == mButtonPositive && mButtonPositiveMessage != null) { m = Message.obtain(mButtonPositiveMessage); }

//...some other code if (m != null) { m.sendToTarget(); } // Post a message so we dismiss after the above handlers are executed. mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface) .sendToTarget(); }};

Fix?

Check the square blogWrapper for clickListener and clear it on detach

Or

Send idle messages to clean app the messages

https://corner.squareup.com/2015/08/a-small-leak.html

Good Practices

Do not keep long-lived references to a context-activitypublic static Context mContext;

public NoLifeCycleClass(Activity myActivity) {

mContext = (Context) myActivity;}

Try using the context-application instead of a context-activity

StringUtilsUI.doSomeLongRunningTask(getApplicationContext());

Avoid non-static inner classespublic class DialogCountdown extends BaseDialogFragment { private class CountDownHandler extends Handler { //do some work }}

Avoid non-static inner classes private static class CountDownHandler extends Handler {

private final WeakReference<DialogCountdown> mDialogCountdownWeakReference;

public CountDownHandler(DialogCountdown dialogCountdown) { super(); mDialogCountdownWeakReference = new WeakReference<>(dialogCountdown); }

public void handleMessage(Message msg) { if(mDialogCountdownWeakReference.get()!=null) { mDialogCountdownWeakReference.get().onCountDown(); } } }

Clean/Stop all your handlers, animation listeners onDestroy();protected void onStop() { super.onStop(); mHandler.clearAllMessages(); unregisterReceivers(); heatMapsDone(); if (mServiceBound) { mServiceBound = false; Services.unbindFromRidesService(this, this); } if (mMapStateMachine != null) { mMapStateMachine.stop(); mMapStateMachine = null; }}

Last world...

Today

With Proguard

Jack

Jill

http://trickyandroid.com/the-dark-world-of-jack-and-jill/

Recommended