近期

也有一段时间没更新文章了,最近刚好在写一些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吗?相信你应该可以回答!