NativeAllocationRegistry----通过绑定Java对象辅助回收native对象内存的机制

1 背景

我们平时在开发中有这种需求,java代码中使用了native对象的一个指针,而且需要在java对象回收后,回收相应的native指针,通常我们就是在java对象release的时候,手动调用一下jni方法,将native对象释放,如果java对象被GC回收呢?那么我们就需要NativeAllocationRegistry这个类帮我们解决这个问题

scss 复制代码
public static final NativeAllocationRegistry sRegistry =
        NativeAllocationRegistry.createMalloced(
            JinObjectImpl.class.getClassLoader(), nGetNativeFinalizer());
            
    sRegistry.registerNativeAllocation(this, mNativePtr);
    
    
    其中
    nGetNativeFinalizer()是一个函数指针
    
    static void deleteJinObject(jinworld::JinObject* object) {
    JinHandler::getInstance().postCallback(new FunctionalCallback([object]() {
        delete object;
        return std::any();
     }));
    }

    static jlong getNativeFinalizer(JNIEnv*, jobject) {
        return static_cast<jlong>(reinterpret_cast<uintptr_t>(&deleteJinObject));
    }
​

2 原理

首先是创建NativeAllocationRegistry 实例,并且创建了成员变量freeFunction ,

aosp\libcore\luni\src\main\java\libcore\util\NativeAllocationRegistry.java

arduino 复制代码
public static NativeAllocationRegistry createMalloced(
        @NonNull ClassLoader classLoader, long freeFunction) {
    return new NativeAllocationRegistry(classLoader, freeFunction, DEFAULT_SIZE, true);
}


private NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size,
        boolean mallocAllocation) {
    if (size < 0) {
        throw new IllegalArgumentException("Invalid native allocation size: " + size);
    }
    this.classLoader = classLoader;
    this.freeFunction = freeFunction;
    this.size = mallocAllocation ? (size | IS_MALLOCED) : (size & ~IS_MALLOCED);
}
​

注意freeFunction 是一个函数指针,类型是typedef void (*FreeFunction)(void*);

aosp\libcore\luni\src\main\native\libcore_util_NativeAllocationRegistry.cpp

arduino 复制代码
#include <nativehelper/JNIHelp.h>
#include <nativehelper/jni_macros.h>

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));
    nativeFreeFunction(nativePtr);
}

static JNINativeMethod gMethods[] = {
    NATIVE_METHOD(NativeAllocationRegistry, applyFreeFunction, "(JJ)V"),
};

void register_libcore_util_NativeAllocationRegistry(JNIEnv* env) {
    jniRegisterNativeMethods(env, "libcore/util/NativeAllocationRegistry",
        gMethods, NELEM(gMethods));
}
​

创建完成以后,我们看下sRegistry.registerNativeAllocation(this, mNativePtr);具体做了什么

java 复制代码
public @NonNull Runnable registerNativeAllocation(@NonNull 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);
    // Ensure that cleaner doesn't get invoked before we enable it.
​    ​Reference.reachabilityFence(referent);
    return result;
}
​

这里构建了几个对象

CleanerThunk 实现完成清理的runnable对象

Cleaner 持有当前java对象 this 和 CleanerThunk 的对象,负责和GC中的ReferenceQueue打通的对象

CleanerRunner 持有Cleaner对象,实现对外暴露的runnable对象,上面那个函数获取的就是这个对象。

2.1 手动释放

手动释放比较简单,

(1)创建NativeAllocationRegistry ,并且初始化native对象的释放函数

(2) Runnable runnable = sRegistry.registerNativeAllocation(this, mNativePtr); 注册native对象和java对象绑定

(3)合适时机调用上一步中的runnable的run方法。 相当于CleanerRunner的run函数

java 复制代码
private static class CleanerRunner implements Runnable {
    private final Cleaner cleaner;

    public CleanerRunner(Cleaner cleaner) {
        this.cleaner = cleaner;
    }

    public void run() {
        cleaner.clean();
    }
}
​

然后调用 Cleaner 的clean方法,接着调用thunk的run函数

csharp 复制代码
public void clean() {
    if (!remove(this))
        return;
    try {
        thunk.run();
    } catch (final Throwable x) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    if (System.err ​!= null)
                        new Error("Cleaner terminated abnormally", x)
                            .printStackTrace();
                    System.exit(1);
                    return null;
                }});
    }
}
​

trunk的run函数,最后调用这个jni函数

applyFreeFunction

scss 复制代码
private class CleanerThunk implements Runnable {
    //忽略部分代码
    public void run() {
        if (nativePtr != 0) {
            applyFreeFunction(freeFunction, nativePtr);
            registerNativeFree(size);
        }
    }
}
​

最后是 通过函数指针执行native函数,参数就是那个绑定的native对象。

scss 复制代码
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));
    nativeFreeFunction(nativePtr);
}

static JNINativeMethod gMethods[] = {
    NATIVE_METHOD(NativeAllocationRegistry, applyFreeFunction, "(JJ)V"),
};

void register_libcore_util_NativeAllocationRegistry(JNIEnv* env) {
    jniRegisterNativeMethods(env, "libcore/util/NativeAllocationRegistry",
        gMethods, NELEM(gMethods));
}
​

2.2 自动释放

自动释放相对复杂一些

(1)创建NativeAllocationRegistry ,并且初始化native对象的释放函数

(2) Runnable runnable = sRegistry.registerNativeAllocation(this, mNativePtr); 注册native对象和java对象绑定

(3)当虚拟机ART这边 发生java对象GC的时候,注意到这个java对象,即第二步中绑定的this对象。

我们看他的构造方法

typescript 复制代码
Cleaner cleaner = Cleaner.create(referent, thunk);
 
 public static Cleaner create(Object ob, Runnable thunk) {
    if (thunk == null)
        return null;
    return add(new Cleaner(ob, thunk));
}

// 创建了一个Cleaner对象
private Cleaner(Object referent, Runnable thunk) {
    super(referent, dummyQueue);
    this.thunk = thunk;
}

这里的父类是 PhantomReference虚引用,
​

创建完成后,当java对象 this, 指的是当前java类对象在java堆内存回收的时候,

art\runtime\gc\reference_processor.cc

arduino 复制代码
class ClearedReferenceTask : public HeapTask {
 public:
  explicit ClearedReferenceTask(jobject cleared_references)
      : HeapTask(NanoTime()), cleared_references_(cleared_references) {
  }
  void Run(Thread* thread) override {
    ScopedObjectAccess soa(thread);
    WellKnownClasses::java_lang_ref_ReferenceQueue_add->InvokeStatic<'V', 'L'>(
        thread, soa.Decode<mirror::Object>(cleared_references_));
    soa.Env()->DeleteGlobalRef(cleared_references_);
  }

 private:
  const jobject cleared_references_;
};
​

会调用,java_lang_ref_ReferenceQueue_add这个方法 即

aosp\libcore\ojluni\src\main\java\java\lang\ref\ReferenceQueue.java

ini 复制代码
static void add(Reference<?> list) {
    synchronized (ReferenceQueue.class) {
        if (unenqueued ​== null) {
            unenqueued ​= list;
        } else {
            // Find the last element in unenqueued.
​            ​Reference<?> last = unenqueued;
            while (last.pendingNext != unenqueued) {
              last = last.pendingNext;
            }
            // Add our list to the end. Update the pendingNext to point back to enqueued.
​            ​last.pendingNext = list;
            last = list;
            while (last.pendingNext != list) {
                last = last.pendingNext;
            }
            last.pendingNext = unenqueued;
        }
        ReferenceQueue.class.notifyAll();
    }
}
​

将这个java对象添加到要回收的对象列表中,并且唤醒ReferenceQueue线程。

这个ReferenceQueue线程是什么时候创建的呢?在虚拟机创建的时候,会调用StartDaemonThreads

art\runtime\runtime.cc

ruby 复制代码
void Runtime::StartDaemonThreads() {
  ScopedTrace trace(__FUNCTION__);
  VLOG(startup) << "Runtime::StartDaemonThreads entering";

  Thread* self = Thread::Current();

  DCHECK_EQ(self->GetState(), ThreadState::kRunnable);

  WellKnownClasses::java_lang_Daemons_start->InvokeStatic<'V'>(self);
  if (UNLIKELY(self->IsExceptionPending())) {
    LOG(FATAL) << "Error starting java.lang.Daemons: " << self->GetException()->Dump();
  }

  VLOG(startup) << "Runtime::StartDaemonThreads exiting";
}
​

会调用java_lang_Daemons_start->InvokeStatic<'V'>(self);这个方法,即

aosp\libcore\libart\src\main\java\java\lang\Daemons.java:

java 复制代码
private static final Daemon[] DAEMONS ​= new Daemon[] {
        HeapTaskDaemon.INSTANCE,
        ReferenceQueueDaemon.INSTANCE,
        FinalizerDaemon.INSTANCE,
        FinalizerWatchdogDaemon.INSTANCE,
};
private static CountDownLatch zygoteStartLatch;

private static boolean postZygoteFork​ ​= false;

@UnsupportedAppUsage
public static void start() {
    zygoteStartLatch ​= new CountDownLatch(DAEMONS.length);
    for (Daemon daemon : DAEMONS) {
        daemon.start();
    }
}
​

这里会创建四个线程,分别是HeapTaskDaemon,ReferenceQueueDaemon,FinalizerDaemon,FinalizerWatchdogDaemon,和GC相关的线程,这个我们在性能分析中经常遇到。

我们回到上面:将这个java对象添加到要回收的对象列表中,并且唤醒ReferenceQueue线程。

scala 复制代码
*/
private static class ReferenceQueueDaemon extends Daemon {
    @UnsupportedAppUsage
    private static final ReferenceQueueDaemon INSTANCE​ ​= new ReferenceQueueDaemon();

    // Monitored by FinalizerWatchdogDaemon to make sure we're still working.
​    ​private final AtomicInteger progressCounter = new AtomicInteger(0);

    ReferenceQueueDaemon() {
        super("ReferenceQueueDaemon");
    }

    @Override public void runInternal() {
        FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(FinalizerWatchdogDaemon.RQ_DAEMON);

        // Call once early to reduce later allocation, and hence chance of OOMEs.
​        ​FinalizerWatchdogDaemon.INSTANCE.resetTimeouts();

        while (isRunning()) {
            Reference<?> list;
            try {
                synchronized (ReferenceQueue.class) {
                    if (ReferenceQueue.unenqueued ​== null) {
                        FinalizerWatchdogDaemon.INSTANCE.monitoringNotNeeded(
                                FinalizerWatchdogDaemon.RQ_DAEMON);
                        // Increment after above call. If watchdog saw it active, it should see
​                        ​// the counter update.
​                        ​progressCounter.incrementAndGet();
                        do {
                           ReferenceQueue.class.wait();
                        } while (ReferenceQueue.unenqueued ​== null);
                        progressCounter.incrementAndGet();
                        FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(
                                FinalizerWatchdogDaemon.RQ_DAEMON);
                    }
                    list = ReferenceQueue.unenqueued;
                    ReferenceQueue.unenqueued ​= null;
                }
                ReferenceQueue.enqueuePending(list, progressCounter);
                FinalizerWatchdogDaemon.INSTANCE.resetTimeouts();
            } catch (InterruptedException e) {
                // Happens when we are asked to stop.
​            ​} catch (OutOfMemoryError ignored) {
                // Very unlikely. Cleaner.clean OOMEs are caught elsewhere, and nothing else
​                ​// should allocate regularly. Could result in enqueuePending dropping
​                ​// references. Does occur in tests that run out of memory.
​                ​System.logW(RQD_OOM_MESSAGE);
            }
        }
    }

    Object currentlyProcessing() {
      return ReferenceQueue.getCurrentTarget();
    }
}
​

唤醒后,执行ReferenceQueue.​enqueuePending(list, progressCounter);

ini 复制代码
public static void enqueuePending(Reference<?> list, AtomicInteger progressCounter) {
    Reference<?> start = list;
    do {
        ReferenceQueue queue = list.queue;
        if (queue == null || sun.misc.Cleaner.isCleanerQueue(queue)) {
            Reference<?> next = list.pendingNext;
            // Always make pendingNext a self-loop to preserve the invariant that
​            ​// once enqueued, pendingNext is non-null -- without leaking
​            ​// the object pendingNext was previously pointing to.
​            ​list.pendingNext = list;
            if (queue != null) {
                // This is a Cleaner. Run directly without additional synchronization.
​                ​sun.misc.Cleaner cl = (sun.misc.Cleaner) list;
                currentTarget ​= cl;
                cl.clean();  // Idempotent. No need to check queueNext first. Handles all
​                             ​// exceptions.
​                ​list.queueNext = sQueueNextUnenqueued;
            }
            list = next;
        } else {
            currentTarget ​= queue;
            // To improve performance, we try to avoid repeated
​            ​// synchronization on the same queue by batching enqueueing of
​            ​// consecutive references in the list that have the same
​            ​// queue. We limit this so that progressCounter gets incremented
​            ​// occasionally,
​            ​final int MAX_ITERS = 100;
            int i = 0;
            synchronized (queue.lock) {
                // Nothing in here should throw, even OOME,
​                ​do {
                    Reference<?> next = list.pendingNext;
                    list.pendingNext = list;
                    queue.enqueueLocked(list);
                    list = next;
                } while (list != start && list.queue == queue && ++i < MAX_ITERS);
                queue.lock.notifyAll();
            }
        }
        progressCounter.incrementAndGet();
    } while (list != start);
    currentTarget ​= null;
    sun.misc.Cleaner.checkCleanerQueueEmpty();
}
​

最终执行cl.clean()方法,最后也就调用到了ClearnerTrunk的 ​

applyFreeFunction​(freeFunction, nativePtr)方法。

3 备注

上文内容中

(1)Bitmap也在使用这种方式,来回收对应native内存。

(2)LeakCanary在使用ReferenceQueue原理,来监测内存泄漏。

相关推荐
倾云鹤20 分钟前
okhttp3网络请求
android·网络
二流小码农23 分钟前
鸿蒙开发:了解Canvas绘制
android·ios·harmonyos
行墨1 小时前
Kotlin 函数引用
android
行墨1 小时前
Kotlin内联函数
android
未来猫咪花1 小时前
Flutter 状态管理极速版:view_model
android·flutter
彼方卷不动了1 小时前
【技术学习】在 Android 上用 Kotlin 实现支持多图层的 OpenGL 渲染管线
android·kotlin·opengl
洞见不一样的自己1 小时前
RecyclerView系列之二 ItemDecoration
android
恋猫de小郭1 小时前
Android 转内部开发谁说是闭源?明明 AOSP 外部 PR 支持也会继续
android·前端·flutter
肆仙.2 小时前
单表、多表查询练习
android·adb
Min_小明2 小时前
CMake 简单使用总结
android·开发语言·算法