点赞关注,不再迷路,你的支持对我意义重大!
🔥 Hi,我是丑丑。本文 GitHub · Android-NoteBook 已收录,这里有 Android 进阶成长路线笔记 & 博客,欢迎跟着彭丑丑一起成长。(联系方式在 GitHub)
前言
NativeAllocationRegistry是Android 8.0(API 27)引入的一种辅助回收native内存的机制,使用步骤并不复杂,但是关联的Java原理知识却不少- 这篇文章将带你理解
NativeAllocationRegistry的原理,并分析相关源码。如果能帮上忙,请务必点赞加关注,这真的对我非常重要。
目录
- 使用步骤
从Android 8.0(API 27)开始,Android中很多地方可以看到NativeAllocationRegistry的身影,我们以Bitmap为例子介绍NativeAllocationRegistry的使用步骤,涉及文件:Bitmap.java、Bitmap.h、Bitmap.cpp
步骤1:创建 NativeAllocationRegistry
首先,我们看看实例化NativeAllocationRegistry的地方,具体在Bitmap的构造函数中:
1 | java复制代码// # Android 8.0 |
可以看到,Bitmap的构造函数(在从JNI中调用)中实例化了NativeAllocationRegistry,并传递了三个参数:
| 参数 | 解释 |
|---|---|
classLoader |
加载freeFunction函数的类加载器 |
freeFunction |
回收native内存的native函数直接地址 |
size |
分配的native内存大小(单位:字节) |
步骤2:注册对象
紧接着,调用了registerNativeAllocation(...),并传递两个参数:
| 参数 | 解释 |
|---|---|
referent |
Java层对象的引用 |
nativeBitmap |
native层对象的地址 |
1 | arduino复制代码// Bitmap.java |
步骤3:回收内存
完成前面两步后,当Java层对象被垃圾回收后,NativeAllocationRegistry会自动回收注册的native内存。例如,我们加载几张图片,随后释放Bitmap的引用,可以观察到GC之后,native层的内存也自动回收了:
1 | scss复制代码tv.setOnClickListener{ |
- GC 前的内存分配情况 —— Android 8.0
- GC 后的内存分配情况 —— Android 8.0
- 提出问题
掌握了NativeAllocationRegistry的作用和使用步骤后,很自然地会有一些疑问:
- 为什么在
Java层对象被垃圾回收后,native内存会自动被回收呢? NativeAllocationRegistry是从Android 8.0(API 27)开始引入,那么在此之前,native内存是如何回收的呢?
通过分析NativeAllocationRegistry源码,我们将一步步解答这些问题,请继续往下看。
- NativeAllocationRegistry 源码分析
现在我们将视野回到到NativeAllocationRegistry的源码,涉及文件:NativeAllocationRegistry.java 、NativeAllocationRegistry_Delegate.java、libcore_util_NativeAllocationRegistry.cpp
3.1 构造函数
1 | arduino复制代码// NativeAllocationRegistry.java |
可以看到,NativeAllocationRegistry的构造函数只是将三个参数保存下来,并没有执行额外操作。以Bitmap为例,三个参数在Bitmap的构造函数中获得,我们继续上一节未完成的分析过程:
- 分析点 1:native 层需要的内存大小
1 | arduino复制代码// Bitmap.java |
可以看到,nativeSize 由固定的32字节加上getAllocationByteCount(),总之,NativeAllocationRegistry需要一个native层内存大小的参数,这里就不展开了。关于Bitmap内存分配的详细分析请务必阅读文章:《Android | 各版本中 Bitmap 内存分配对比》
- 分析点 2:回收函数 nativeGetNativeFinalizer()
1 | arduino复制代码// Bitmap.java |
可以看到,nativeGetNativeFinalizer()是一个native函数,返回值是一个long,这个值其实相当于Bitmap_destruct()函数的直接地址。很明显,Bitmap_destruct()就是用来回收native层内存的。
那么,Bitmap_destruct()是在哪里调用的呢?继续往下看!
- 分析点 3:加载回收函数的类加载器
1 | arduino复制代码// Bitmap.java |
另外,NativeAllocationRegistry还需要ClassLoader参数,文档注释指出:classloader是加载freeFunction所在native库的类加载器,但是NativeAllocationRegistry内部并没有使用这个参数。这里笔者也不理解为什么需要传递这个参数,如果有知道答案的小伙伴请告诉我一下~
3.2 注册对象
1 | java复制代码// Bitmap.java |
可以看到,registerNativeAllocation (...)方法参数是**Java层对象引用与native层对象的地址**。函数体乍一看是有点绕,笔者在这里也停留了好长一会。我们简化一下代码,try-catch代码先省略,函数返回值Runnable暂时用不到也先省略,瘦身后的代码如下:
1 | java复制代码// NativeAllocationRegistry.java |
看到这里,上文提出的第一个疑问就可以解释了,原来NativeAllocationRegistry内部是利用了sun.misc.Cleaner.java机制,简单来说:使用虚引用得知对象被GC的时机,在GC前执行额外的回收工作。若还不了解Java的四种引用类型,请务必阅读:《Java | 引用类型》
# 举一反三
DirectByteBuffer内部也是利用了Cleaner实现堆外内存的释放的。若不了解,请务必阅读:《Java | 堆内存与堆外内存》
1 | csharp复制代码private class CleanerThunk implements Runnable { |
继续往下看,CleanerThunk 其实是Runnable的实现类,run()在Java层对象被垃圾回收时触发,主要做了两件事:
- 分析点 4:执行内存回收方法
1 | arduino复制代码public static native void applyFreeFunction(long freeFunction, long nativePtr); |
可以看到,applyFreeFunction(...)最终就是执行到了前面提到的内存回收函数,对于Bitmap就是Bitmap_destruct()
- 分析点 5:注册 / 注销native内存
1 | arduino复制代码// NativeAllocationRegistry.java |
向VM注册native内存,比便在内存占用达到界限时触发GC,在该native内存回收时,需要向VM注销该内存量
- 对比 Android 8.0 之前回收 native 内存的方式
前面我们已经分析完NativeAllocationRegistry的源码了,我们看一看在Android 8.0之前,Bitmap是用什么方法回收native内存的,涉及文件:Bitmap.java (before Android 8.0)
1 | java复制代码// before Android 8.0 |
如果理解了NativeAllocationRegistry的源码,上面这段代码就很好理解呀!
- 共同点:
- 分配的
native层内存需要向VM注册 / 注销 - 通过一个
native层的内存回收函数来回收内存
- 分配的
- 不同点:
NativeAllocationRegistry依赖于sun.misc.Cleaner.javaBitmapFinalizer依赖于Object#finalize()
我们知道,finalize()在Java对象被垃圾回收时会调用,BitmapFinalizer就是利用了这个机制来回收native层内存的。若不了解,请务必阅读文章:《Java | 谈谈我对垃圾回收的理解》
再举几个常用的类在Android 8.0之前的源码为例子,原理都大同小异:Matrix.java (before Android 8.0)、Canvas.java (before Android 8.0)
1 | java复制代码// Matrix.java |
- 问题回归
NativeAllocationRegistry利用虚引用感知Java对象被回收的时机,来回收native层内存- 在
Android 8.0 (API 27)之前,Android通常使用Object#finalize()调用时机来回收native层内存
推荐阅读
- Java | 带你理解 ServiceLoader 的原理与设计思想
- Android | 谈一谈 Matrix 与坐标变换
- Android | 一文带你全面了解 AspectJ 框架
- Android | 使用 AspectJ 限制按钮快速点击
- Android | 这是一份详细的 EventBus 使用教程
- 开发者 | 浅析App社交分享的5种形式
- 计算机组成原理 | Unicode 和 UTF-8是什么关系?
- 计算机组成原理 | 为什么浮点数运算不精确?(阿里笔试)
感谢喜欢!你的点赞是对我最大的鼓励!欢迎关注彭旭锐的GitHub!
本文转载自: 掘金