advanced memory management on ios and android - mark probst and rodrigo kumpera

Post on 18-Nov-2014

2.537 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

 

TRANSCRIPT

Rodrigo KumperaRuntime EngineerXamarinkumpera@xamarin.com

AdvancedMemory Management

oniOS and Android

Mark ProbstRuntime EngineerXamarinmark@xamarin.com

AdvancedMemory Management

oniOS and Android

What a Garbage Collector Does

• Gives you memory for your objects• Computes which objects are reachable• Gets rid of the rest (= garbage)

h"p://www.flickr.com/photos/tweng/2235972313/

object referenceobject withreference

one object ... … referencing ... … another

Basic Garbage Collection

Root

Garbage Collection

Root

Garbage Collection

Root

Garbage Collection

Root

Garbage Collection

Root

Garbage Collection

Root

Garbage Collection

Root

Garbage Collection

Root

Garbage Collection

Two Generations

• Generational hypothesisMost objects die young

• NurseryWhere (small) objects are born

• MajorWhere they are copied to when they mature

• Large Object SpacePart of the major generation, objects > 8KbTypically large arrays

Stop-the-World

• Nursery collection pauses are short• Major collection pauses can take a long time• All threads registered with the runtime are stopped

That includes the main run loop thread

• iOS animations continue to run in a separate process

Common Issues

• Let’s examine three common issues• Given a trivial piece code

Find the issueUnderstand the problemLearn how to fix it

010203040506070809101112131415

iOS Puzzle 1public  override  void  ViewDidLoad  (){   var  imageView  =  new  UIImageView  (IMG_VIEW_POSITION);   var  image  =  UIImage.FromBundle  ("grumpy-­‐cat.jpg");   imageView.Image  =  image;   View.Add  (imageView);     var  button  =  UIButton.FromType  (UIButtonType.RoundedRect);   button.Frame  =  BUTTON_POSITION;   View.Add  (button);     button.TouchUpInside  +=  (sender,  e)  =>  {     imageView.RemoveFromSuperview  ();   };}

010203040506070809101112131415

Something Is Missingpublic  override  void  ViewDidLoad  (){   var  imageView  =  new  UIImageView  (IMG_VIEW_POSITION);   var  image  =  UIImage.FromBundle  ("grumpy-­‐cat.jpg");   imageView.Image  =  image;   View.Add  (imageView);     var  button  =  UIButton.FromType  (UIButtonType.RoundedRect);   button.Frame  =  BUTTON_POSITION;   View.Add  (button);     button.TouchUpInside  +=  (sender,  e)  =>  {     imageView.RemoveFromSuperview  ();   };}

The garbage collector cannot see what’sbehind an innocent object

The Illusion

32  bytes

2  MbC#  UIImage

ObjC  UIImage

0102030405060708091011121314151617

Dispose Your Resourcespublic  override  void  ViewDidLoad  (){   var  imageView  =  new  UIImageView  (IMG_VIEW_POSITION);   var  image  =  UIImage.FromBundle  ("grumpy-­‐cat.jpg");   imageView.Image  =  image;   View.Add  (imageView);     var  button  =  UIButton.FromType  (UIButtonType.RoundedRect);   button.Frame  =  BUTTON_POSITION;   View.Add  (button);     button.TouchUpInside  +=  (sender,  e)  =>  {     imageView.RemoveFromSuperview  ();     imageView.Dispose  ();     image.Dispose  ();   };}

Using Dispose

• Call Dispose() to release ownership of a resource• Use with large native resources, such as

ImagesSounds

• Or scarce resources, such asFilesSockets

• Remember, lifecycle must be manually defined

01020304050607080910111213141516

iOS Puzzle 2public  class  CustomView  :  UIView  {   UIViewController  parent;     public  CustomView  (UIViewController  parent)   {     this.parent  =  parent;   }}

public  class  Puzzle2Controller  :  UIViewController{   public  override  void  ViewDidLoad  ()   {     View.Add  (new  CustomView  (this));   }}

01020304050607080910111213141516

Indirect Cyclespublic  class  CustomView  :  UIView  {   UIViewController  parent;     public  CustomView  (UIViewController  parent)   {     this.parent  =  parent;   }}

public  class  Puzzle2Controller  :  UIViewController{   public  override  void  ViewDidLoad  ()   {     View.Add  (new  CustomView  (this));   }}

Cycles in Objective-C

1

1

How those cycles happenIndirect Cycles

C#

Puzzle2Controller

CustomView

View.Add  (...)

this.parent  =  ...

Objec;ve-­‐C

1

2

01020304050607080910111213141516

Using Weak Referencespublic  class  CustomView  :  UIButton  {   WeakReference<UIViewController>  parent;     public  CustomView  (UIViewController  parent)   {     this.parent  =  new  WeakReference<UIViewController>  (parent);   }}

public  class  Puzzle2Controller  :  UIViewController{   public  override  void  ViewDidLoad  ()   {     View.Add  (new  CustomView  (this));   }}

Indirect cycles

• Inherited from Objective-C• How to detect

When multiple objects point to each other• Breaking those cycles

Use WeakReferenceBy disposing the parentBy explicitly nulling links

• What can trigger it under the hoodNon-wrapper subclasses of NSObjectWith reference count > 2

0102030405060708091011121314

iOS Puzzle 2 (Bonus)public  class  CustomButton  :  UIButton  {   public  CustomButton  ()  {}}

public  class  Puzzle2Controller  :  UIViewController{   public  override  void  ViewDidLoad  ()   {     var  button  =  new  CustomButton  ();     View.Add  (button);     button.TouchUpInside  +=  (sender,  e)  =>         this.RemoveFromParentViewController  ();   }}

0102030405060708091011121314

Watch Your Lambdaspublic  class  CustomButton  :  UIButton  {   public  CustomButton  ()  {}}

public  class  Puzzle2Controller  :  UIViewController{   public  override  void  ViewDidLoad  ()   {     var  button  =  new  CustomButton  ();     View.Add  (button);     button.TouchUpInside  +=  (sender,  e)  =>         this.RemoveFromParentViewController  ();   }}

Measure before assuming something is wrongEnabling GC logging on Android

A Small Detour

$adb shell setprop debug.mono.env "MONO_LOG_LEVEL=debug|MONO_LOG_MASK=gc"

D/Mono ( 5862): GC_MAJOR: (user request) pause 2.82ms, total 3.06ms, bridge 20.96 major 608K/808K los 9K/20K

0102030405060708091011

Android Puzzlepublic  class  Tweet  {}

public  class  Puzzle1  :  ListActivity{   protected  override  void  OnCreate  (Bundle  bundle)   {     base.OnCreate  (bundle);     var  data  =  new  Tweet[]  {  tweet0,  tweet1,  tweet2,  tweet3  };     ListAdapter  =  new  ArrayAdapter  (this,  Resource.Layout.TextViewItem,  data);   }}

0102030405060708091011

Over-Sharingpublic  class  Tweet  {}

public  class  Puzzle1  :  ListActivity{   protected  override  void  OnCreate  (Bundle  bundle)   {     base.OnCreate  (bundle);     var  data  =  new  Tweet[]  {  tweet0,  tweet1,  tweet2,  tweet3  };     ListAdapter  =  new  ArrayAdapter  (this,  Resource.Layout.TextViewItem,  data);   }}

A tale of two heapsCross-Heap References

ArrayAdapter

ArrayList

Tweets

C#  Object

Java  Object

01020304050607080910111213141516171819

Do It All from C# Landpublic  class  Tweet  {}

public  class  TweetAdapter  :  BaseAdapter<Tweet>  {   List<Tweet>  tweets;   public  override  Tweet  this[int  position]  {     get  {  return  tweets  [position];  }   }   public  override  int  Count  {     get  {  return  tweets.Count;  }   }}

public  class  Puzzle3  :  ListActivity{   protected  override  void  OnCreate  (Bundle  bundle)  {     var  data  =  new  List<Tweet>  ()  {  tweet0,  tweet1,  tweet2,  tweet3  };     ListAdapter  =  new  TweetAdapter  (this,  data);   }}

The a!er effectCross-Heap References

TweetsAdapter

List<Tweet>

Tweets

C#  Object

Java  Object

Avoid Cross-Heap References

• It’s expensive for Java to see a C# objectAnd vice-versa

• Performance cost of language crossing• Higher Garbage Collector costs• Your objects are effectively being mirrored

So using twice as much memory

Performance Tips

• The less you allocate, the less o!en the GC runs• The less live data you have, the quicker the GC runs• Small, short-lived objects are cheap• Don’t allocate large (> 8Kb) objects that die young• Avoid writing to reference fields• Better: avoid having reference fields• Don’t use free lists

Q&A

Use SGen unless you havegood reason not to

SGen vs Boehm

Memory Management on iOS

• Reference Counting• Each object has a reference count field• Incremented when something points to it• Decremented when something stops pointing to it• Object is released when count equals to zero

Xamarin.iOS Approach

• Two classes of objects• Wrappers

Comes from native frameUIButton, NSString, etc

• User typesUser code that extend the aboveMyButton

Wrappers

• Retain on construction• Release on finalization/dispose• We don’t care if the managed object goes away

User Types

• Retain/Release like wrappers• Can have custom state• We make a gc handle (think of a static field) point to the object if

reference count > 1• Managed object is kept around if native is interested in it

0102030405060708091011

User Typesclass  MyButton  :  UIButton  {}...

public  override  void  ViewDidLoad  ()

{

  var  button  =  new  MyButton  ();  //ref  count  ==  1,  no  gc  handle

  this.View.Add  (button);  //ref  count  ==  2,  gc  handle  created

  ...

  button.RemoveFromSuperView  ();  //  ref  count  ==  1,  gc  handle  removed

}

First Rule of Finalizers:Don’t Use Finalizers!

• They’re not guaranteed to run within any deadline• They don’t run in a specific sequence• They make objects live longer• The GC doesn’t know about unmanaged resources

Finalizers

• Run in a dedicated finalizer thread• Run concurrently with other threads• Keep their objects and those referenced from there alive for

another GC round

Roothas

Finalizer

an object with a finalizer must be kept aliveFinalizer Illustration

Roothas

Finalizer

so must all objects reachable from itFinalizer Illustration

Roothas

Finalizer

so must all objects reachable from itFinalizer Illustration

Roothas

Finalizer

they die in the next collectionFinalizer Illustration

Root

they die in the next collectionFinalizer Illustration

Roothas

Finalizer

unless ...Finalizer Illustration

Roothas

Finalizer

the finalizer resurrects themFinalizer Illustration

Root

the finalizer resurrects themFinalizer Illustration

Root

the finalizer resurrects themFinalizer Illustration

Stuff You Can Do with Finalizers(and Probably Shouldn’t)

• Resurrect their objects• Re-register for finalization• Non-critical vs critical finalizers

Nursery

Root 1 Root 2

Major Heap

keeping track of major→nursery referencesThe Write Barrier

Nursery

Root 1 Root 2

Major Heap

keeping track of major→nursery referencesThe Write Barrier

What Are Roots?

• static variables

• Stack frames in managed threads• Finalizer queue• References registered in unmanaged code

01020304050607

Android Puzzle 2public  class  CommitLogAdapter  :  BaseAdapter  {   Dictionary<string,  CommitObject>  commitCache;       CommitObject  GetCommit  (string  hash)  {     return  commitCache  [hash];   }}

01020304050607

Too Many Objectspublic  class  CommitLogItemAdapter  :  BaseAdapter  {   Dictionary<string,  CommitObject>  commitCache;       CommitObject  GetCommit  (string  hash)  {     return  commitCache  [hash];   }}

It must inspect special objects andeverything they reference

Android GC Interop

CommitLogAdapter

commitsCache

01020304050607

Use Static Cachespublic  class  CommitLogItemAdapter  :  BaseAdapter  {   static  Dictionary<string,  CommitObject>  commitCache;       static  CommitObject  GetCommit  (string  hash)  {     return  commitCache  [hash];   }}

Taking care of special objects

• If it extends the Android framework, it’s a special object• The Garbage Collector scans them in a different way

That is slowerIt rescans all objects they referenceAvoid pointing to large group of objects

• Explicitly manage the lifecycle of long living objectsStatic caches and collectionsHelps measuring consumption

top related