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