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

相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone1 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen1 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone1 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui