关于android:JNI之缓存与引用

5次阅读

共计 2923 个字符,预计需要花费 8 分钟才能阅读完成。

导读

在后面《JNI 之拜访 java 属性和办法》一文中咱们介绍了在 JNI 办法中拜访 java 类对象的属性和办法,试想一下如果每次调用 JNI 办法都通过查找类,查找办法 id,最初实现调用,这里是否有性能问题呢?
而且 FindClass/GetMethodID/GetFieldID 这些函数自身就含有一些性能问题,既然如此,那是否将 FindClass/GetMethodID/GetFieldID 这些函数的调用后果缓存起来,下次再进入时间接应用缓存即可呢?必定是能够的。

JNI 援用类型

在 JNI 中蕴含三种援用类型,它们别离是全局援用、部分援用和弱全局援用。

1、部分援用
部分援用又称为本地援用,部分援用会在函数完结时主动开释,例如函数 FindClass 返回的就是一个部分援用。须要留神的是部分援用的个数是无限的,个别是 512 个,所以部分援用能手动开释的尽早手动进行开释,特地是在一些循环体内,应用结束马上进行开释,否则很可能会超出部分援用个数的限度。

同时部分援用还会阻止对象被回收,也就是影响 GC,所以更应该在不须要时尽早进行开释,通过函数 DeleteLocalRef 能够开释部分援用。

不能在本地办法中通过动态变量来贮存本地援用,并在后续调用中应用雷同的援用,因为本地援用是会被主动开释的。

部分援用仅仅在创立它的线程中是无效的。在线程 A 中被创立的部分援用是不可能在线程 B 中被应用的。千万不要将一个部分援用保留在全局变量中给其余线程应用。

如果你应用 AttachCurrentThread 连贯(attach)了 Native 过程,正在运行的代码在线程拆散(detach)之前绝不会主动开释部分援用。使用者创立的任何部分援用必须手动删除。

2、全局援用
全局援用不会在函数完结时主动开释,如果没有开释则会在程序运行期间始终保留,所以特地适宜用来做一些缓存的操作。

不同于部分援用,全局援用是能够跨线程应用的,只有它不被程序员手动开释就会始终无效。

通过函数 NewGlobalRef 能够创立一个全局援用,当你的本地代码不再须要拜访一个全局援用时,你应该调用 DeleteGlobalRef 办法。如果你遗记调用这个函数,虚拟机将无奈通过垃圾收集器回收相应的对象,即便这个对象再也不会在零碎的其余中央中应用。

留神 jfieldID 和 jmethodID 是映射类型(opaque types),不是对象援用,不应该被传入到 NewGlobalRef。原始数据指针,像 GetStringUTFChars 和 GetByteArrayElements 的返回值,也都不是对象(它们可能在线程间传递,并且在调用对应的 Release 函数之前都是无效的)。

3、弱全局援用
弱全局援用不像部分援用哪样在函数完结时主动开释,也不像全局援用哪有始终保留在内存中,弱全局援用在 GC 的时候是可能会被垃圾回收的,因而每次在应用弱全局援用之前都须要进行判空解决。能够通过函数 IsSameObject 与 NULL 比照判断以后弱全局援用是否还无效。绝不要在 Native 代码中用 == 符号来比拟两个援用。

通过函数 NewWeakGlobalRef 能够创立一个弱全局援用,当你的本地代码不再须要拜访一个弱全局援用时,你应该调用 DeleteWeakGlobalRef 函数开释掉弱全局援用。如果你遗记调用这个函数,Java 虚拟机依然可能通过垃圾收集器收集底层对象,然而将无奈回收该弱全局援用对象占用的内存。

缓存策略

通过下面 JNI 三种援用的介绍咱们晓得,如果须要做缓存的话,应用全局援用就特地适宜了。

平时咱们在做缓存策略的时候个别会面临着两种抉择,一种是相似懒加载式的缓存策略,就是在第一次调用时如果为空则进行缓存;另外一种是在动态代码块中进行缓存。那么这两种缓存策略又什么区别呢,孰优孰劣呢?

针对第一次应用时缓存判空缓存策略须要在每一次应用时对相干资源进行判空校验,且可能在多线程应用情况下产生线程平安的问题;而且这种策略仅仅在类没有被卸载的时候是无效的,开发者必须保障在你的代码还依赖你所缓存的这些 ID 的过程中,你所应用的类没有被卸载或者从新加载。

应用类动态初始化式缓存策略,被缓存的资源在类被卸载并从新加载时候会被主动从新计算,并且在多线程状况下不会有线程平安的问题。因而倡议应用类的动态初始化代码段中计算并缓存属性或者办法 ID 等相干资源。

以下是一个应用类动态代码块缓存属性 ID 的一个简略 demo:

MainActivity.java

public class MainActivity extends AppCompatActivity {
    static {System.loadLibrary("jnitest");
    }

    private int age = 16;

    @Override
    protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.sample_text);
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                age++;
                Person person = new Person(age);
                person.changeAge();
                person.printAge();}
        });
    }

}

Person.java

public class Person {

    private int age;

    Person(int age){this.age = age;}

    public void printAge(){Log.v("Person_Tag","age:" + age);
    }

    static {initIDs();
    }
    // 初始化缓存 id
    private native static void initIDs();
    // 通过 JNI 批改年龄
    public native void changeAge();}

native-lib.cpp

extern "C"
JNIEXPORT void JNICALL
Java_com_fly_jnitest_Person_initIDs(JNIEnv *env, jclass clazz) {
    // 缓存,弱全局援用
    ageFieldID = env->GetFieldID(clazz,"age","I");
}

extern "C"
JNIEXPORT void JNICALL
Java_com_fly_jnitest_Person_changeAge(JNIEnv *env, jobject thiz) {if(nullptr != ageFieldID){jint age = env->GetIntField(thiz,ageFieldID);
        env->SetIntField(thiz,ageFieldID,age + 1);
    }
}

系列举荐

JNI 根底简介
JNI 之数组与字符串的应用
JNI 之动静注册与动态注册
JNI 之拜访 java 属性和办法

关注我,一起提高,人生不止 coding!!!

正文完
 0