Smart Pointers

Baiqin Wang
13 min readFeb 4, 2021

--

The native IBinder and BpRefBase classes inherit from a base class RefBase. In the article "Binder architecture and core components" I skipped it to focus on the key topics of that article, but now it is time to look into it. If you are creating a class that needs to be managed by Android smart pointer framework, it should inherit from RefBase or LightRefBase. Smart pointers is not only used by Binder. In contrast, it is used ubiquitously by Android native frameworks. The main reason I put this article in the middle of the Binder series is that the knowledge about it is key to understanding Binder object lifetime management, which we will talk about in another article.

Smart pointer framework provides a solution for automatic memory recycling in Android native framework which is written in C++. As we know, many VM based languages like Java provide some garbage collection mechanisms to free developers from managing memory deallocation. However, programs written in C++ need to manually free up unused memory by using the delete operator. One reason that garbage collection is not achievable in C++ is that memory referencing semantics cannot be recognized by a C++ compiler. For example, if there is a long variable in C++ with value 0xc3452200, is it just a numerical value or referencing a range of memory? C++ compiler won't know that, only code developers know. Without the ability to recognize memory reference, garbage collection in C++ is not possible. Even though native garbage collection in C++ is not possible, the smart pointer framework provides a reference counting based solution to automatic memory deallocation. But that doesn't mean C++ compilers can recognize primitive memory referencing magically using it. In order to enable automatic memory deallocation, you are constrained to use certain utility classes to manage your classes.

Strong pointer

Android smart pointer framework uses a reference counting mechanism to achieve automatic memory deallocation. The most basic form of reference counting just maintains an integer reference counter which is initialized to zero when an object is first created. The reference counter gets incremented each time the object is referenced and decremented each time the object is dereferenced. Once the reference counter drops to zero, the underlying object gets automatically deallocated. Android framework provides a LightRefBase class to support this basic form of reference counting and a class that needs this basic form of reference counting support should inherit from it:

The mCount field is an integer reference counter that's initialized to zero at first. incStrong and decStrong will be invoked when the inherited class is referenced and dereferenced respectively. Note that if the reference counter drops to zero, decStrong will free the object memory on line 14. The generic type T represents the inherited class who inherits from LightRefBase. An example of such a class is DisplayDevice:

A class that inherits from LightRefBase essentially just adds a mCount field to it's class declaration, the incStrong and decStrong won't get automatically invoked when referencing and dereferencing happen. In order for the two method to be called automatically, we need to find a mechanism to recognize referencing and dereferencing on the class and invoke the incStrong and decStrong accordingly. We still cannot use primitive types to reference the object. If you store the memory address of a DisplayDevice to a long or void *, who knows whether you are holding a reference to the object or just want to print out value to stdout? So in order to recognize object references, memory referencing itself needs to be abstracted in an object oriented way. Android framework provides a sp class to abstract memory referencing as a non-primitive type:

The generic type T is the actual class type that this sp points to and the m_ptr field is a primitive pointer to that class. The term sp is an acronym for "strong pointer" and it is used to acquire a strong reference to an object. Holding a strong reference to an object will prevent the object from being deallocated. In addition to sp, there is another reference type wp which stands for "weak pointer" and it is used to hold a weak reference to an object. Holding a weak reference to an object will not affect the lifetime of the object but you cannot access the object with a wp directly either. You need to first promote the weak reference to a strong reference before accessing the object. In general, as soon as the strong reference count drops to zero the object will be deallocated, after which trying to promote the weak reference yields a null pointer. The sp and wp classes represent object references in object oriented fashion and you need to use them to reference objects if you want to enable reference counting. With the LightRefBase class who has only an integer counter, only strong reference counting can be implemented. We need to use RefBase to implement both strong and weak reference counting, which will talk about later.

The PoolThread is a real Android framework class we have seen before and it inherits from RefBase. To show you some examples, basic usages of smart pointers is as follows:

There are many other patterns of smart pointer usages but we cannot cover them all.

Now object references are materialized as wp and sp classes, how do we write some "driver" code to recognize and perform reference counting changes? Suppose sp and wp are built into C++ language and you can only use them to manage object references , then the compiler will have sufficient context to implement reference counting from the program's syntax structure. At the end of day, the way you recognize object referencing is through syntax analysis. For example, you can recognize line 2 above as object referencing because there is an assignment operator "=". Luckily, C++ language provides an operator overloading mechanism with which you can plugin code onto the program's syntax structure and operator overloading is exactly the "driver" mechanism to implement reference counting in smart pointer framework.

Let’s now look at how sp class is implemented:

As you can see, the sp class overloads a lot of operators to make the behavior of sp correct and intuitive. For example, the "*" operator passes through to the underlying primitive reference and the "->" operator returns the underlying reference. The assignment operator overriding is arguably the most important ones. It is called when an assignment such as line 2 in the above sample code happens. In this case, the passed in other points to the newly created PoolThread so line 48 will increment the strong reference counting on it. The oldPtr is empty since the sp class is just created. Line 51 assigns the passed in PoolThread pointer to m_ptr field. The end result is that the strong reference counting of the PoolThread is changed to 1. Now suppose you later assigns a nullptr to the sp instance. The other parameter is null in this case and the oldPtr variable will be the original PoolThread pointer. So line 50 will decrement the strong reference count of the PoolThread to 0 and the PoolThread will be deallocated because of that. The program only compiles when the generic type T has declaredincStrong and decStrong methods, which both LightRefBase and RefBase do.

Weak pointer

Many operators in sp are overloaded to adapt different use cases. I cannot cover them all but the code base should be easy to understand.

There is a fundamental problem with strong pointer only reference counting: loop references cannot be automatically deallocated. Consider the following program:

There is no way to access the Display and Computer instances after line 22. However the two objects each have a strong reference to the other so the strong reference counter of both objects is 1. There is no way one of the strong reference count can drop to 0 in the future so the two object are leaked forever. The way to address this dilemma is to introduce a new kind of reference type wp. The sp usually indicates an ownership relationship and wpdoesn't. So a preferred way to address this loop referencing issue is making the mComputer field in Display a weak pointer wp and let at least Computer inherit RefBase instead of LightRefBase. With this change in place, no strong references to computer remains after line 22 so it will be deallocated. When computer is being destructed, the destructor of sp in Computer will be invoked to decrement strong reference count on display, which will cause display to be deallocated as well. The memory leak is solved with the help of wp.

I just mentioned you need to use RefBase to implement weak reference counting, let's have a look at its declaration:

Many of the methods inside RefBase has an id argument which should point to a memory address you own. You should usually pass in the memory address of the object from which you call the method. It is only for debugging purposes though, we can ignore this argument during code analysis. The weakref_type inside RefBase is an interface class for weak reference management and its implementation class is weakref_impl. The incWeak and decWeak interface methods increments and decrements the object's weak reference count respectively. The attemptIncStrong method tries to increment the strong reference count on the target object and it is mainly used by wp to perform weak pointer promotion. It should return true if it is possible to increment the strong reference count on the target object. attemptIncWeak is used to address a corner case in Binder only, we will not talk about it in this article. The refBase method returns the corresponding RefBase class it points to. By definition, an object's weak reference can outlive its strong reference. So these weak reference related methods must live in another class because the object itself will be deallocated when the last strong reference on it is removed. The mRefs field will be allocated at the time when RefBase is allocated. A class (mainly wp class) who wants to manage the object's weak reference counting should use createWeak or getWeakRefs to get a hold the weak reference type.‌

The weakref_impl class implements the weakref_type interface. The majority of its code base is for debugging purpose so I am not pasting them. By definition, when an object's strong reference count is incremented, its weak reference count should be incremented as well. So an object's weak reference count is always greater or equal to strong reference count. So weakref_impl also needs to know the strong reference count to implement weak reference count correctly. mStrong and mWeak in weakref_impl are the strong reference count and weak reference count respectively. So there is no reason to keep a strong reference counter in RefBase itself anymore. However, RefBase should still have the incStrong and decStrong interface methods for sp to use but the way these two methods are implemented are drastically different than LightRefBase. mStrong is initialized to INITIAL_STRONG_VALUE instead of 0 because the value 0 means the object has strong references previously but lost all of them. mBase in weakref_impl points to the linked RefBase class. You can set a OBJECT_LIFETIME_STRONG or OBJECT_LIFETIME_WEAK mask on the mFlags field and you should use extendObjectLifetime method to set the flag. OBJECT_LIFETIME_STRONG is the default value and it defines the classical object lifetime that an object will be deallocated as long as the last strong reference on it is removed. However, if the lifetime type isOBJECT_LIFETIME_WEAK then the object will only gets deallocated when the last weak reference on it is gone. This feature is only used by BpBinder and BpRefBase in Binder framework.‌

onFirstRef is a callback method that a subclass of RefBase can implement and it is invoked when the strong reference on it is incremented for the first time. In contrast, onLastStrongRef will be invoked when the last strong reference on the object is removed. onLastWeakRef is invoked when the last weak reference on the object is gone. Of course this only makes sense when mFlags is OBJECT_LIFETIME_WEAK, otherwise the object is already deallocated when the last weak reference on it is removed. You cannot invoke a callback on a deallocated object. forceIncStrong is different than incStrong in that it will trigger onFirstRef even if the mStrong is 0. Of course this only makes sense when mFlags is OBJECT_LIFETIME_WEAK, otherwise as soon as mStrong drops to 0 the object will be deallocated, there is no way you can get a strong reference on it again. If an object has flag OBJECT_LIFETIME_WEAK and doesn't have a strong reference, onIncStrongAttempted determines whether you can promote the weak reference to a strong reference. A subclass of RefBase can override this method based on whether it makes sense to promote the weak reference to a strong one. This is only used by BpBinder and BpRefBase in Android and it always returns false since cross process promoting is not supported by Binder driver right now.

This RefBase class looks much more complicated than LightRefBase. It is indeed more complicated but quite a lot of the functionalities are specifically used by Binder to handle some corner case usages.

Now let’s look at the declaration of wp class:

Most of the methods are similar to that of sp so I am not pasting them. What's interesting is that there is a new method promote to promote the wp to a sp. Besides, wp hold a reference to the native memory reference and the weak reference type.

Let’s look at two methods in this class. The first one is the method of the overloaded assignment operator. If the new object on the right side of assignment is not nullptr, then line 7 calls the createWeak method to get the weakref_type inside the object and increments its weak reference count. Line 8 calls decWeak to decrease the weak reference count of the original object this wp references.

The promote just calls attemptIncStrong to check whether it is possible to promote a strong reference from the weakref_type and returns a promoted sp if so.

Let’s now look at how RefBase implements these reference counting methods.

The incStrong method will be called when an object is referenced by sp. Line 6 and 7 increases weak reference count and strong reference count respectively. The incWeak will be called when an object is referenced by wp. As we can see, a strong reference also increments weak reference count but a weak reference only increases weak reference count. The fetch_add is an atomic function to add a value to a variable and returns its original value. Line 8 checks whether the strong reference count is INITIAL_STRONG_VALUE before incrementing the strong reference count, if it is then this is the first time a strong reference is acquired, so line 11 subtracts INITIAL_STRONG_VALUE from mStrong to get the correct strong count which is 1. Then line 13 invokes the onFirstRef callback. The incWeak method just decrements mWeak by 1.

The decStrong method decrements mStrong on line 6 and invokes decWeak to decrement weak reference count on line 14. The variable c stores the value of mStrong before decrementing. If it is 1 then line 8 calls onLastStrongRef callback. If the mFlags field is OBJECT_LIFETIME_STRONG, which is the normal case, then the current object can be deallocated because all strong references have been removed.

The decWeak method decrements mWeak on line 20. If mWeak doesn't drop to 0 after decrement, then line 21 will just return. If the mFlags is OBJECT_LIFETIME_STRONG and this object had strong references previously, then 28 just deletes the weak reference type. Note that when weak reference count drops to zero, so does its strong reference count. So the object itself must have been deallocated in decStrong. Right now its weak reference count also drops to zero, then we just deallocate the weakref_impl to free up all memory. However, if this object never had strong references before, current implementation does nothing. In a previous RefBase implementation [1], this corner case is handled by deleting impl->mBase. This operation will invoke the destructor of RefBase which will eventually deallocate the weakref_impl itself. The previous code base makes more sense to me but it is just handling some corner use cases anyways. If the lifetime of this object is OBJECT_LIFETIME_WEAK, then onLastWeakRef callback will be invoked and impl->mBase will be deallocated. This deallocation will invoke the destructor of RefBase and causes the weak reference type to be deallocated on line 41.

The method attemptIncStrong tries to increment the strong reference count and returns true if the increment is successful or false otherwise. It is used by wp to implement the promote method. A wp needs to be promoted to sp before using. First let's go through a normal case where the object has strong references currently. Line 5 first increments weak reference count because incrementing strong reference count always increments weak reference count. However if this method fails, decWeak will be called to revert the change at line 21, 33 or 38. Line 8 atomically loads the mStrong value into curCount and line 11 tries to increment the mStrong value. The compare_exchange_weak function atomically compares the object value (mStrong) with the first argument value (curCount), if they are equal, then the second argument will be loaded into the object (mStrong), otherwise the object value (mStrong) will be loaded into the first argument (curCount). If there is no other contending thread, this function just increments mStrong in one attempt. We assumed there is strong references on the object currently, so this method will just return at line 53.‌

However, things get complicated when the object had never been strongly referenced or its strong reference count has dropped to zero. If the object lifetime is OBJECT_LIFETIME_STRONG, you definitely cannot get a strong reference on the object if the strong reference count dropped to zero because the object's memory must had been deallocated. This case is handled by line 20 to 23. Otherwise, it means the mStrong is INITIAL_STRONG_VALUE so the object was never strongly referenced. In this case, the object is still in memory so a strong reference can be acquired. Line 25 to 35 rechecks the mStrong value in case another thread modified the strong reference count. Let's assume there is no thread contention, then line 48 to 51 will offset the initial mStrong value and return true.

If the object’s lifetime is OBJECT_LIFETIME_WEAK then the object's memory is not deallocated yet so it is fine to acquire a strong reference. What the actual code does is letting the subclass of RefBase determine whether a strong reference can be acquired. If onIncStrongAttempted returns true then a strong reference can be acquired otherwise not. It's not clear why this onIncStrongAttempted is used for OBJECT_LIFETIME_WEAK. The OBJECT_LIFETIME_WEAK flag is only used by BpBinder and BpRefBase classes in Android, so does the onIncStrongAttempted callback. This doesn't seem to be a generic mechanism to be used elsewhere.

There are other smart pointer functionalities that are strictly designed for Binder, so I am not covering them in this article. In short, Android smart pointer framework provides a reference counting based solution for automatic object deallocation and it is widely used in Android native frameworks.

External links

[1] https://android-review.googlesource.com/c/platform/system/core/+/253186

--

--