likes
comments
collection
share

当调用GetFloatArrayElements iscopy为非空时,返回一定是JNI_TRUE吗

作者站长头像
站长
· 阅读数 4

近期

也有一段时间没更新文章了,最近刚好在写一些jni相关的函数调用。JNI函数真的是太多了,一段时间不写很容易忘记。

这里比较推荐官方的手册,至少API很全Java官方JNI手册

GetFloatArrayElements 方法

当我想要获取一个jfloatArray 的内容时,通常我们会调用以下JNI方法

jfloat* (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*);

这个方法返回值是jfloat* ,同时接受三个参数,前两个参数比较好理解,一个是JNIEnv,是当前JNI环境的一个指针,通常我们都可以通过JavaVM或者默认JNI函数中获取,还有一个就是jfloatArray,代表着我想要读取的数组,最后一个参数就比较有意思了,是否是拷贝

下面举一个例子

声明一个数组,在自定义类中

val test = FloatArray(10)

@JvmStatic
fun getFloatArray():FloatArray{
    return test
}

C代码调用getFloatArray获取Kotlin数组

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,那么这个参数是怎么使用呢?

当调用GetFloatArrayElements iscopy为非空时,返回一定是JNI_TRUE吗

Java官方JNI手册 我们可以看到,当最后一个参数isCopy不为NULL时,isCopy会被设定为JNI_TRUE(常量1),反之是JNI_FALSE(0)

实际上,我们按照上面例子多加一行打印确认,发现也确实是1

jboolean flag = 1;
float *native_array = (*env)->GetFloatArrayElements(env, floatArray, &flag);
__android_log_print(ANDROID_LOG_ERROR, "hello", "flag is %d", flag);

当调用GetFloatArrayElements iscopy为非空时,返回一定是JNI_TRUE吗

那么事情到这里了,对于一个Java开发者来说,应该是结束了,但是对于Android开发者来说,故事还没那么简单。

这里给大家提出一个小疑问:有没有情况,即使GetFloatArrayElements第三个参数isCopy传递为一个非NULL的值,会导致isCopy 为false呢?答案是有的,我们把目光转到ART虚拟机上。

ART虚拟机针对JNI实现

上面我们提到Java官方JNI手册,它算是一个协议,商定了Java虚拟机的初步协定,包括JNI,而在ART虚拟机中,针对JNI的实现在jni_internal.cc

这里我们看到GetPrimitiveArray在ART的实现

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对象呢?

当调用GetFloatArrayElements iscopy为非空时,返回一定是JNI_TRUE吗

其实最终会调用到FindContinuousSpaceFromAddress方法

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中存在着非常多的内存模型划分,比如

// 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

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

当调用GetFloatArrayElements iscopy为非空时,返回一定是JNI_TRUE吗

Space

virtual bool IsContinuousSpace() const {
    return false;
}

Space中默认方法是返回false,但是它的子类ContinueSpace会重写返回true

当调用GetFloatArrayElements iscopy为非空时,返回一定是JNI_TRUE吗

而我们熟知的大对象LargeObjectSpace,其实还是返回false!

让IsMovableObject返回false

回到GetPrimitiveArray方法,我们就知道了,如果属于大对象,那么被分配到的LargeObjectSpace后,那么Runtime::Current()->GetHeap()->IsMovableObject(array) 就会返回false!大对象的定义我们可以在Heap分配时知道,当分配的属于数组或者string大等于large_object_threshold_(默认12kb)时,就会被分配到LargeObjectSpace

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

val test = FloatArray(1024*1024)

@JvmStatic
fun getFloatArray():FloatArray{
    return test
}

修改后我们再执行

jboolean flag = 1;
float *native_array = (*env)->GetFloatArrayElements(env, floatArray, &flag);
__android_log_print(ANDROID_LOG_ERROR, "hello", "flag is %d", flag);

当调用GetFloatArrayElements iscopy为非空时,返回一定是JNI_TRUE吗 达到了我们验证的目的!

最后

本次实验初衷是让读者们知道,ART虚拟机中,其实会有一部分属于定制操作,区别于普通的Java虚拟机规范,我们了解ART具体实现之后,能让我们更加了解这些现象。

下次面试官再问到,当调用GetFloatArrayElements iscopy为非空时,返回一定是JNI_TRUE吗?相信你应该能够回答!