Smart Pointers
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 wp
doesn'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