Like attention, no more lost, your support means a lot to me!

🔥 Hi, I’m Chouchou. GitHub · Android-Notebook has been included in this article. Welcome to grow up with Chouchou Peng. (Contact information at GitHub)

preface

  • NativeAllocationRegistryisAndroid 8.0 (API 27)An auxiliary collection introducednativeThe mechanism of using memory is not complicated, but relatedJavaThere is a lot of knowledge about principles
  • This article will take you throughNativeAllocationRegistryPrinciple, and analysis of the relevant source code. Please be sure to like and follow if you can help, it really means a lot to me.

directory

1. Procedure

Starting from Android 8.0 (API 27), NativeAllocationRegistry can be seen in many places of Android. We take Bitmap as an example to introduce the steps of using NativeAllocationRegistry, involving files: Java, bitmap. h, bitmap. CPP

Step 1: Create the NativeAllocationRegistry

First, let’s look at where the NativeAllocationRegistry is instantiated, specifically in the Bitmap constructor:

Java // called from JNI Bitmap(long nativeBitmap,...) {// omit other code... Long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount(); long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount(); NativeGetNativeFinalizer (); // Bitmap. Class. GetClassLoader () 】 NativeAllocationRegistry registry = new NativeAllocationRegistry ( Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize); / / registered Java object references and native layer registry. The object's address registerNativeAllocation (this, nativeBitmap); } private static final long NATIVE_ALLOCATION_SIZE = 32; private static native long nativeGetNativeFinalizer();Copy the code

As you can see, the Bitmap constructor (called from JNI) instantiates the NativeAllocationRegistry and passes three parameters:

parameter explain
classLoader loadingfreeFunctionFunction class loader
freeFunction recyclingnativeThe memory ofnativeFunction direct address
size The distribution ofnativeMemory size (in bytes)

Step 2: Register objects

Next, registerNativeAllocation(…) is called. And passes two arguments:

parameter explain
referent JavaA reference to a layer object
nativeBitmap nativeThe address of the layer object
// Bitmap.java // called from JNI Bitmap(long nativeBitmap,...) {// omit other code... / / registered Java object references and native layer registry. The object's address registerNativeAllocation (this, nativeBitmap); } / / NativeAllocationRegistry. Java public Runnable registerNativeAllocation (Object referent, long nativePtr) {/ / code omitted, It is added that... }Copy the code

Step 3: Reclaim memory

After completing the previous two steps, when Java layer objects are garbage collected, the NativeAllocationRegistry will automatically reclaim the registered native memory. For example, if we load several images and then release the Bitmap reference, we can observe that after GC, the native layer’s memory is also automatically recycled:

tv.setOnClickListener{
    val map = HashSet<Any>()
    for(index in 0 .. 2){
        map.add(BitmapFactory.decodeResource(resources,R.drawable.test))
    }
Copy the code
  • Memory allocation before GC — Android 8.0

  • Memory allocation after GC — Android 8.0


2. Ask questions

Now that you know what the NativeAllocationRegistry does and how to use it, it’s natural to have some questions:

  • Why is itJavaAfter the layer object is garbage collected,nativeWill memory be reclaimed automatically?
  • NativeAllocationRegistryfromAndroid 8.0 (API 27)I’m going to introduce, so before I do that,nativeHow is memory reclaimed?

We will answer these questions step by step by analyzing the NativeAllocationRegistry source code, please read on.


3. Source code analysis of NativeAllocationRegistry

Now let’s return to the source of NativeAllocationRegistry, which involves files: NativeAllocationRegistry. Java, NativeAllocationRegistry_Delegate. Java, libcore_util_NativeAllocationRegistry. CPP

3.1 Constructors

/ / NativeAllocationRegistry. Java public class NativeAllocationRegistry {/ / loaded freeFunction function class loaders private final ClassLoader classLoader; Private final Long freeFunction; // Allocate native memory size (bytes) private final long size; public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) { if (size < 0) { throw new IllegalArgumentException("Invalid native allocation size: " + size); } this.classLoader = classLoader; this.freeFunction = freeFunction; this.size = size; }}Copy the code

As you can see, the constructor of NativeAllocationRegistry simply saves the three parameters and does nothing extra. In the case of Bitmap, three parameters are obtained in the constructor of the Bitmap. We continue the analysis we left off in the previous section:

  • Analysis Point 1: Memory size required by native layer
Long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount(); long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount(); public final int getAllocationByteCount() { if (mRecycled) { Log.w(TAG, "Called getAllocationByteCount() on a recycle()'d bitmap! " + "This is undefined behavior!" ); return 0; } / / call native methods return nativeGetAllocationByteCount (mNativePtr); } private static final long NATIVE_ALLOCATION_SIZE = 32;Copy the code

As you can see, nativeSize consists of a fixed 32 bytes plus getAllocationByteCount(). In short, NativeAllocationRegistry requires a native layer memory size parameter, which will not be expanded here. Detailed analysis on the distribution of the Bitmap memory, please be sure to read the article: the Android | versions of Bitmap memory allocation contrast”

  • Analysis Point 2: Recycling function nativeGetNativeFinalizer()
// bitmap.java // NativeGetNativeFinalizer ()】 NativeAllocationRegistry Registry = new NativeAllocationRegistry( Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize); private static native long nativeGetNativeFinalizer(); / / / / Java layer -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - / / / / Bitmap native layer. CPP static jlong Bitmap_getNativeFinalizer(JNIEnv*, Return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Bitmap_destruct)); } static void Bitmap_destruct(BitmapWrapper* bitmap) { delete bitmap; }Copy the code

NativeGetNativeFinalizer () is a native function that returns a long, which is the direct address of the Bitmap_destruct() function. Obviously, Bitmap_destruct() is used to recycle native layer memory.

So, where is Bitmap_destruct() called? Keep reading!

  • Analysis point 3: The classloader that loads the reclaim function
// Bitmap.java
Bitmap.class.getClassLoader()
Copy the code

In addition, NativeAllocationRegistry also requires the ClassLoader parameter, and the documentation notes that: The classloader is the classloader that loads the native library where the freeFunction resides, but this parameter is not used inside the NativeAllocationRegistry. I don’t understand why this parameter needs to be passed. If you know the answer, please let me know ~

3.2 Registering Objects

/ / Bitmap. Java / / registered Java object references and native layer registry. The object's address registerNativeAllocation (this, nativeBitmap); // NativeAllocationRegistry.java public Runnable registerNativeAllocation(Object referent, long nativePtr) { if (referent == null) { throw new IllegalArgumentException("referent is null"); } if (nativePtr == 0) { throw new IllegalArgumentException("nativePtr is null"); } CleanerThunk thunk; CleanerRunner result; try { thunk = new CleanerThunk(); Cleaner cleaner = Cleaner.create(referent, thunk); result = new CleanerRunner(cleaner); registerNativeAllocation(this.size); } catch (VirtualMachineError vme /* probably OutOfMemoryError */) { applyFreeFunction(freeFunction, nativePtr); throw vme; // Other exceptions are impossible. // Enable the cleaner only after we can no longer throw anything, including OOME. thunk.setNativePtr(nativePtr); return result; }Copy the code

As you can see, registerNativeAllocation (…) The method argument is the address of the Java layer object reference and native layer object. The function body is a bit convoluted at first glance, and I’ve been here for quite a while. The try-catch code is omitted, and the return value Runnable is not needed.

/ / NativeAllocationRegistry. Java / / (simplified) public void registerNativeAllocation (Object referent, long nativePtr) { CleanerThunk thunk thunk = new CleanerThunk(); Cleaner = cleaner. create(referent, thunk); // Register native memory registerNativeAllocation(this.size); thunk.setNativePtr(nativePtr); } private class CleanerThunk implements Runnable {// }Copy the code

The original NativeAllocationRegistry uses the sun.misc.cleaner.java mechanism internally. In simple terms, it uses virtual references to know when objects are GC and performs additional collection before GC. If you don’t understand Java four reference types, please be sure to read: “the Java | reference type”

# Draw a Parallel

Inside DirectByteBuffer is also the use of Cleaner to achieve the release of memory out of the heap. If don’t understand, please be sure to read: “the Java | heap memory and heap memory”

Private class CleanerThunk implements Runnable {private long nativePtr; public CleanerThunk() { this.nativePtr = 0; } public void run() { if (nativePtr ! // 【 解 析 解 析 】 applyFreeFunction(freeFunction, nativePtr); RegisterNativeFree (size); // } } public void setNativePtr(long nativePtr) { this.nativePtr = nativePtr; }}Copy the code

Moving on, CleanerThunk is actually an implementation of Runnable, and run(), which is triggered when Java layer objects are garbage collected, does two things:

  • Analysis point 4: Perform the memory reclamation method
public static native void applyFreeFunction(long freeFunction, long nativePtr); // NativeAllocationRegistry.cpp typedef void (*FreeFunction)(void*); static void NativeAllocationRegistry_applyFreeFunction(JNIEnv*, jclass, jlong freeFunction, jlong ptr) { void* nativePtr = reinterpret_cast<void*>(static_cast<uintptr_t>(ptr)); FreeFunction nativeFreeFunction = reinterpret_cast<FreeFunction>(static_cast<uintptr_t>(freeFunction)); // Call the reclaim function nativeFreeFunction(nativePtr); }Copy the code

You can see that applyFreeFunction(…) The result is the execution of the previously mentioned memory reclamation function, which for bitmaps is Bitmap_destruct().

  • Analysis Point 5: Register/deregister native memory
/ / NativeAllocationRegistry. Java / / registered native memory registerNativeAllocation (enclosing the size). // Register native memory registerNativeFree(size); / / note: Private static void registerNativeAllocation(long Size) {private static void registerNativeAllocation(long size) { VMRuntime.getRuntime().registerNativeAllocation((int)Math.min(size, Integer.MAX_VALUE)); } private static void registerNativeFree(long size) { VMRuntime.getRuntime().registerNativeFree((int)Math.min(size, Integer.MAX_VALUE)); }Copy the code

Register native memory with VM, and GC will be triggered when the memory usage reaches the limit. When the native memory is reclaimed, the memory amount needs to be deregistration to VM


4. Compare the way native memory was reclaimed before Android 8.0

Before Android 8.0, bitmap.java (before Android 8.0) is used to retrieve native memory.

// Before Android 8.0 // bitmap.java private Final Long mNativePtr; private final BitmapFinalizer mFinalizer; // called from JNI Bitmap(long nativeBitmap,...) {// omit other code... mNativePtr = nativeBitmap; mFinalizer = new BitmapFinalizer(nativeBitmap); int nativeAllocationByteCount = (buffer == null ? getByteCount() : 0); mFinalizer.setNativeAllocationByteCount(nativeAllocationByteCount); } private static class BitmapFinalizer { private long mNativeBitmap; private int mNativeAllocationByteCount; BitmapFinalizer(long nativeBitmap) { mNativeBitmap = nativeBitmap; } public void setNativeAllocationByteCount(int nativeByteCount) { if (mNativeAllocationByteCount ! = 0) {/ / registration native memory VMRuntime getRuntime (). RegisterNativeFree (mNativeAllocationByteCount); } mNativeAllocationByteCount = nativeByteCount; if (mNativeAllocationByteCount ! = 0) {/ / cancellation of native memory VMRuntime layer. The getRuntime (). RegisterNativeAllocation (mNativeAllocationByteCount); } } @Override public void finalize() { try { super.finalize(); } catch (Throwable t) { // Ignore } finally { setNativeAllocationByteCount(0); // Execute the memory destructor (mNativeBitmap) function; mNativeBitmap = 0; } } } private static native void nativeDestructor(long nativeBitmap);Copy the code

If you understand the source code of NativeAllocationRegistry, the above code is easy to understand!

  • Thing in common:
    • The distribution ofnativeLayer memory needs to be directedVMRegister/Unregister
    • Through anativeLayer’s memory reclamation function to reclaim memory
  • Difference:
    • NativeAllocationRegistryDepends on thesun.misc.Cleaner.java
    • BitmapFinalizerDepends on theObject#finalize()

As we know, Finalize () is called when Java objects are garbage collected. BitmapFinalizer uses this mechanism to reclaim native layer memory. If don’t understand, please be sure to read the article: “the Java | talk about my understanding of recycling”

Matrix. Java (before Android 8.0), Canvas. Java (before Android 8.0)

// Matrix.java @Override protected void finalize() throws Throwable { try { finalizer(native_instance); } finally { super.finalize(); } } private static native void finalizer(long native_instance); // Canvas.java private final CanvasFinalizer mFinalizer; private static final class CanvasFinalizer { private long mNativeCanvasWrapper; public CanvasFinalizer(long nativeCanvas) { mNativeCanvasWrapper = nativeCanvas; } @Override protected void finalize() throws Throwable { try { dispose(); } finally { super.finalize(); } } public void dispose() { if (mNativeCanvasWrapper ! = 0) { finalizer(mNativeCanvasWrapper); mNativeCanvasWrapper = 0; }}} public Canvas() {// omit other code... mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper); }Copy the code

5. Problem regression

  • NativeAllocationRegistryUse virtual reference awarenessJavaWhen an object is collected, to be collectednativeLayer of memory
  • inThe Android 8.0 (API) 27Before,AndroidUsually useObject#finalize()Call timing to recyclenativeLayer of memory

Recommended reading

  • Java | show you understand the ServiceLoader principle and design idea
  • Android | to talk about a Matrix and the coordinate transformation
  • Android | article bring you a comprehensive understanding of AspectJ framework
  • Android | use AspectJ limit button click quickly
  • Android | this is a detailed EventBus use tutorial
  • Developers | five kinds of forms of social sharing App is analysed
  • Computer composition principle | Unicode utf-8 and what is the relationship?
  • | why floating-point arithmetic of computer constitute principle is not accurate? (Ali written test)

Thank you! Your “like” is the biggest encouragement for me! Welcome to attentionPeng XuruiThe lot!