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