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原理,来监测内存泄漏。

相关推荐
lilili啊啊啊1 小时前
iOS safari和android chrome开启网页调试与检查器的方法
android·ios·safari
Blue.ztl4 小时前
菜鸟之路day31一一MySQL之多表设计
android·数据库·mysql
练习本7 小时前
Android系统架构模式分析
android·java·架构·系统架构
每次的天空12 小时前
Kotlin 内联函数深度解析:从源码到实践优化
android·开发语言·kotlin
练习本12 小时前
Android MVC架构的现代化改造:构建清晰单向数据流
android·架构·mvc
早上好啊! 树哥13 小时前
android studio开发:设置屏幕朝向为竖屏,强制应用的包体始终以竖屏(纵向)展示
android·ide·android studio
YY_pdd13 小时前
使用go开发安卓程序
android·golang
Android 小码峰啊15 小时前
Android Compose 框架物理动画之捕捉动画深入剖析(29)
android·spring
bubiyoushang88815 小时前
深入探索Laravel框架中的Blade模板引擎
android·android studio·laravel
cyy29815 小时前
android 记录应用内存
android·linux·运维