点赞关注,不再迷路,你的支持对我意义重大!
🔥 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.java
BitmapFinalizer
依赖于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!
本文转载自: 掘金