近期
也有一段时间没更新文章了,最近刚好在写一些jni相关的函数调用。JNI函数真的是太多了,一段时间不写很容易忘记。
这里比较推荐官方的手册,至少API很全Java官方JNI手册
GetFloatArrayElements 方法
当我想要获取一个jfloatArray 的内容时,通常我们会调用以下JNI方法
scss
jfloat* (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*);
这个方法返回值是jfloat* ,同时接受三个参数,前两个参数比较好理解,一个是JNIEnv,是当前JNI环境的一个指针,通常我们都可以通过JavaVM或者默认JNI函数中获取,还有一个就是jfloatArray,代表着我想要读取的数组,最后一个参数就比较有意思了,是否是拷贝
下面举一个例子
声明一个数组,在自定义类中
kotlin
val test = FloatArray(10)
@JvmStatic
fun getFloatArray():FloatArray{
return test
}
C代码调用getFloatArray获取Kotlin数组
ini
jclass clz = (*env)->GetObjectClass(env, thiz);
jmethodID id = (*env)->GetStaticMethodID(env, clz, "getFloatArray", "()[F");
jfloatArray floatArray = (*env)->CallStaticObjectMethod(env, clz, id);
jsize size = (*env)->GetArrayLength(env, floatArray);
float *native_array = (*env)->GetFloatArrayElements(env, floatArray, NULL);
for (int i = 0; i < size; ++i) {
__android_log_print(ANDROID_LOG_ERROR, "hello", "i is %f", native_array[i]);
}
(*env)->ReleaseFloatArrayElements(env, floatArray, native_array, 0);
从AOSP上可以看到,大部分使用到GetFloatArrayElements的时候,最后一个参数都是NULL,那么这个参数是怎么使用呢?
从Java官方JNI手册 我们可以看到,当最后一个参数isCopy不为NULL时,isCopy会被设定为JNI_TRUE(常量1),反之是JNI_FALSE(0)
实际上,我们按照上面例子多加一行打印确认,发现也确实是1
ini
jboolean flag = 1;
float *native_array = (*env)->GetFloatArrayElements(env, floatArray, &flag);
__android_log_print(ANDROID_LOG_ERROR, "hello", "flag is %d", flag);
那么事情到这里了,对于一个Java开发者来说,应该是结束了,但是对于Android开发者来说,故事还没那么简单。
这里给大家提出一个小疑问:有没有情况,即使GetFloatArrayElements第三个参数isCopy传递为一个非NULL 的值,会导致isCopy 为false呢?答案是有的,我们把目光转到ART虚拟机上。
ART虚拟机针对JNI实现
上面我们提到Java官方JNI手册,它算是一个协议,商定了Java虚拟机的初步协定,包括JNI,而在ART虚拟机中,针对JNI的实现在jni_internal.cc 中
这里我们看到GetPrimitiveArray在ART的实现
ini
template <typename ArrayT, typename ElementT, typename ArtArrayT>
static ElementT* GetPrimitiveArray(JNIEnv* env, ArrayT java_array, jboolean* is_copy) {
CHECK_NON_NULL_ARGUMENT(java_array);
ScopedObjectAccess soa(env);
ObjPtr<ArtArrayT> array = DecodeAndCheckArrayType<ArrayT, ElementT, ArtArrayT>(
soa, java_array, "GetArrayElements", "get");
if (UNLIKELY(array == nullptr)) {
return nullptr;
}
注意这里
if (Runtime::Current()->GetHeap()->IsMovableObject(array)) {
if (is_copy != nullptr) {
*is_copy = JNI_TRUE;
}
const size_t component_size = sizeof(ElementT);
size_t size = array->GetLength() * component_size;
void* data = new uint64_t[RoundUp(size, 8) / 8];
memcpy(data, array->GetData(), size);
return reinterpret_cast<ElementT*>(data);
} else {
我们的答案出现了
if (is_copy != nullptr) {
*is_copy = JNI_FALSE;
}
return reinterpret_cast<ElementT*>(array->GetData());
}
}
这里我们就找到了刚刚的问题,即使is_copy不为NULL,也会为JNI_FALSE的情况。Runtime::Current()->GetHeap()->IsMovableObject(array),这里有一个关键的函数,我们知道ART有着自己的内存管理,不同于一般Java虚拟机,这里我们知道,如果分配的array不是一个Movable的对象,那么即使is_copy不为NULL,也会返回JNI_FALSE。
那么如何定义Movable对象呢?
其实最终会调用到FindContinuousSpaceFromAddress方法
rust
space::ContinuousSpace* Heap::FindContinuousSpaceFromAddress(const mirror::Object* addr) const {
for (const auto& space : continuous_spaces_) {
if (space->Contains(addr)) {
return space;
}
}
return nullptr;
}
这个方法非常简单,就是看我们分配的array指针在哪一块Space上,如果它在continuous_spaces_上,其实就会返回true!
Space划分
我们在ART内存模型这一篇中提到,ART中存在着非常多的内存模型划分,比如
c
// All-known continuous spaces, where objects lie within fixed bounds.
std::vector<space::ContinuousSpace*> continuous_spaces_ GUARDED_BY(Locks::mutator_lock_);
// All-known discontinuous spaces, where objects may be placed throughout virtual memory.
std::vector<space::DiscontinuousSpace*> discontinuous_spaces_ GUARDED_BY(Locks::mutator_lock_);
// All-known alloc spaces, where objects may be or have been allocated.
std::vector<space::AllocSpace*> alloc_spaces_;
// A space where non-movable objects are allocated, when compaction is enabled it contains
// Classes, ArtMethods, ArtFields, and non moving objects.
space::MallocSpace* non_moving_space_;
// Space which we use for the kAllocatorTypeROSAlloc.
space::RosAllocSpace* rosalloc_space_;
// Space which we use for the kAllocatorTypeDlMalloc.
space::DlMallocSpace* dlmalloc_space_;
Space 属于哪一块,会在虚拟机Heap初始化时加入AddSpace
scss
void Heap::AddSpace(space::Space* space) {
CHECK(space != nullptr);
WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_);
前提
if (space->IsContinuousSpace()) {
DCHECK(!space->IsDiscontinuousSpace());
space::ContinuousSpace* continuous_space = space->AsContinuousSpace();
// Continuous spaces don't necessarily have bitmaps.
accounting::ContinuousSpaceBitmap* live_bitmap = continuous_space->GetLiveBitmap();
accounting::ContinuousSpaceBitmap* mark_bitmap = continuous_space->GetMarkBitmap();
// The region space bitmap is not added since VisitObjects visits the region space objects with
// special handling.
if (live_bitmap != nullptr && !space->IsRegionSpace()) {
CHECK(mark_bitmap != nullptr);
live_bitmap_->AddContinuousSpaceBitmap(live_bitmap);
mark_bitmap_->AddContinuousSpaceBitmap(mark_bitmap);
}
这里就加入了上面看到的continuous_spaces_
continuous_spaces_.push_back(continuous_space);
// Ensure that spaces remain sorted in increasing order of start address.
std::sort(continuous_spaces_.begin(), continuous_spaces_.end(),
[](const space::ContinuousSpace* a, const space::ContinuousSpace* b) {
return a->Begin() < b->Begin();
});
} else {
CHECK(space->IsDiscontinuousSpace());
space::DiscontinuousSpace* discontinuous_space = space->AsDiscontinuousSpace();
live_bitmap_->AddLargeObjectBitmap(discontinuous_space->GetLiveBitmap());
mark_bitmap_->AddLargeObjectBitmap(discontinuous_space->GetMarkBitmap());
discontinuous_spaces_.push_back(discontinuous_space);
}
if (space->IsAllocSpace()) {
alloc_spaces_.push_back(space->AsAllocSpace());
}
}
它的前提是space->IsContinuousSpace()返回true
我们再来看一下ART虚拟机内存模型,其中有一个很关键的抽象父类Space
csharp
Space
virtual bool IsContinuousSpace() const {
return false;
}
Space中默认方法是返回false,但是它的子类ContinueSpace会重写返回true
而我们熟知的大对象LargeObjectSpace,其实还是返回false!
让IsMovableObject返回false
回到GetPrimitiveArray方法,我们就知道了,如果属于大对象,那么被分配到的LargeObjectSpace后,那么Runtime::Current()->GetHeap()->IsMovableObject(array) 就会返回false!大对象的定义我们可以在Heap分配时知道,当分配的属于数组或者string大等于large_object_threshold_(默认12kb)时,就会被分配到LargeObjectSpace
rust
art/runtime/gc/heap-inl.h
inline bool Heap::ShouldAllocLargeObject(ObjPtr<mirror::Class> c, size_t byte_count) const {
// We need to have a zygote space or else our newly allocated large object can end up in the
// Zygote resulting in it being prematurely freed.
// We can only do this for primitive objects since large objects will not be within the card table
// range. This also means that we rely on SetClass not dirtying the object's card.
return byte_count >= large_object_threshold_ && (c->IsPrimitiveArray() || c->IsStringClass());
}
因此,我们只需要把数组改大一些,就能让iscopy返回false
kotlin
val test = FloatArray(1024*1024)
@JvmStatic
fun getFloatArray():FloatArray{
return test
}
修改后我们再执行
ini
jboolean flag = 1;
float *native_array = (*env)->GetFloatArrayElements(env, floatArray, &flag);
__android_log_print(ANDROID_LOG_ERROR, "hello", "flag is %d", flag);
达到了我们验证的目的!
最后
本次实验初衷是让读者们知道,ART虚拟机中,其实会有一部分属于定制操作,区别于普通的Java虚拟机规范,我们了解ART具体实现之后,能让我们更加了解这些现象。
下次面试官再问到,当调用GetFloatArrayElements iscopy为非空时,返回一定是JNI_TRUE吗?相信你应该能够回答!