FFmpeg In Android - JNI文档-2

8 Additional JNI Features

8.1 JNI and Threads

Java VM支持多线程的并发执行,并发大大增加了复杂度,之前单线程时从没遇到过的.多线程可能同时访问同一个对象,同一个文件描述符,简单来说就是临界资源.为了最有效地使用本节,你应该事先对多线程的概念较为熟悉.你应该知道怎样用Java语言使用多线程,同步访问临界资源等,可以参考Concurrent Programming in Java.

8.1.1 Constraints

本地代码运行在多线程环境时,有些约束条件必须牢记。理解了这些约束,不管本地函数有多少线程在并发,代码执行都会很安全。

  • JNIEnv指针不能跨线程使用,不能将其缓存起来又在其他线程使用;
  • 局部引用只在创建它的线程中有效,不能将局部引用从一个线程传递到另外一个线程,如果需要这样做,要将局部引用转换为全局引用。

8.1.2 Monitor Entry and Exit

监视器是Java平台上的原始同步机制,每个对象都可以与监视器动态关联.JNI允许你使用监视器进行同步,这样相当于Java语言的代码块同步:

synchronized (obj) {
... // synchronized block
}

Java VM确保一个线程执行代码块之前首先与监视器关联,这样在一个时刻内最多只有一个线程持有监视器并执行代码块。等待其他线程退出监视器时,一个线程会阻塞。
本地代码可以使用JNI函数在JNI引用上实现同步功能,使用MonitorEnter进入监视器,MonitorExit退出监视器:

if ((*env)->MonitorEnter(env, obj) != JNI_OK) {
	... /* error handling */
}
... /* synchronized block */
if ((*env)->MonitorExit(env, obj) != JNI_OK) {
	... /* error handling */
};

如果线程尚未持有监视器就调用MonitorExit,会出现IllegalMonitorStateException异常。JNI函数可能会调用失败,必须检查返回值。MonitorEnter, MonitorExit与jclass, jstring, jarray等类型一起使用,这些都是jobject类型。

8.1.3 Monitor Wait and Notify

Java API包含其他一些对线程同步很有用的方法,Object.wait, Object.notify, Object.notifyAll,JNI没有对应的函数,而是使用回调Java API的办法来代替:

/* precomputed method IDs */
static jmethodID MID_Object_wait;
static jmethodID MID_Object_notify;
static jmethodID MID_Object_notifyAll;
void JNU_MonitorWait(JNIEnv *env, jobject object, jlong timeout)
{
	(*env)->CallVoidMethod(env, object, MID_Object_wait,
	timeout);
}
void JNU_MonitorNotify(JNIEnv *env, jobject object)
{
	(*env)->CallVoidMethod(env, object, MID_Object_notify);
}
void JNU_MonitorNotifyAll(JNIEnv *env, jobject object)
{
	(*env)->CallVoidMethod(env, object, MID_Object_notifyAll);
}

上面假设methodID分别对应了Object.wait, Object.notify, Object.notifyAll并已经在其他地方初始化了。

8.1.4 Obtaining a JNIEnv Pointer in Arbitrary Contexts

前面说过JNIEnv指针只在单个线程有效,一般来说没什么问题,因为本地函数可以从第一个参数中获得。但有时候有些函数不是Java VM直接调用的,其没有JNIEnv参数,可以调用AttachCurrentThread为当前线程获得JNIEnv指针:

JavaVM *jvm; /* already set */
f()
{
	JNIEnv *env;
	(*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
	... /* use env */
}

当线程Attach到Java VM后,AttachCurrentThread返回,就获得了当前线程的JNIEnv指针了。有很多方式获取到JavaVM指针: 创建Java VM时记录下来,使用JNI_GetCreatedJavaVMs查询,调用JNI函数GetJavaVM,或者在JNI_OnLoad中定义.跟JNIEnv指针不同,JavaVM指针可以缓存在全局引用,跨线程使用。
Java 2 SDK release 1.2 提供了新的函数GetEnv来检查当前线程是否已经Attach到Java VM了,如果是,返回当前线程的JNIEnv指针。如果已经Attach到Java VM,GetEnvAttachCurrentThread的作用是相同的。

8.1.5 Matching the Thread Models

Suppose that native code to be run in multiple threads accesses a global resource. Should the native code use JNI functions MonitorEnter and MonitorExit, or use the native thread synchronization primitives in the host environment (such as mutex_lock on Solaris)? Similarly, if the native code needs to create a new thread,should it create a java.lang.Thread object and perform a callback of Thread.start through the JNI, or should it use the native thread creation primitive in the host environment (such as thr_create on Solaris)?
The answer is that all of these approaches work if the Java virtual machine implementation supports a thread model that matches that used by the native code. The thread model dictates how the system implements essential thread operations such as scheduling, context switching, synchronization, and blocking in system calls. In a native thread model the operating system manages all the essential thread operations. In a user thread model, on the other hand, the application code implements the thread operations. For example, the “Green thread” model shipped with JDK and Java 2 SDK releases on Solaris uses the ANSI C functions setjmp and longjmp to implement context switches.
Many modern operating systems (such as Solaris and Win32) support a native thread model. Unfortunately, some operating systems still lack native thread support. Instead, there may be one or many user thread packages on these operating
systems.
If you write application strictly in the Java programming language, you need not worry about the underlying thread model of the virtual machine implementation. The Java platform can be ported to any host environment that supports the
required set of thread primitives. Most native and user thread packages provide the necessary thread primitives for implementing a Java virtual machine.
JNI programmers, on the other hand, must pay attention to thread models. The application using native code may not function properly if the Java virtual implementation and the native code have a different notion of threading and synchronization. For example, a native method could be blocked in a synchronization operation in its own thread model, but the Java virtual machine, running in a different thread model, may not be aware that the thread executing the native method is blocked. The application deadlocks because no other threads will be scheduled.
The thread models match if the native code uses the same thread model as the Java virtual machine implementation. If the Java virtual machine implementation uses native thread support, the native code can freely invoke thread-related primitives in the host environment. If the Java virtual machine implementation is based on a user thread package, the native code should either link with the same user thread package or rely on no thread operations at all. The latter may be harder to achieve than you think: most C library calls (such as I/O and memory allocation functions) perform thread synchronization underneath. Unless the native code performs pure computation and makes no library calls, it is likely to use thread primitives indirectly.
Most virtual machine implementations support only a particular thread model for JNI native code. Implementations that support native threads are the most flexible, hence native threads, when available, are typically preferred on a given host environment. Virtual machine implementations that rely on a particular user thread package may be severely limited as to the type of native code with which they can operate.
Some virtual machine implementations may support a number of different thread models. A more flexible type of virtual machine implementation may even allow you to provide a custom thread model implementation for virtual machine’s
internal use, thus ensuring that the virtual machine implementation can work with your native code. Before embarking on a project likely to require native code, you should consult the documentation that comes with your virtual machine implementation for thread model limitations.

8.2 Writing Internationalized Code

//TODO

8.3 Registering Native Methods

程序执行一个本地函数之前需要先做两步工作,1,加载包含了本地实现函数的本地库, 2,链接本地库:

  1. System.loadLibrary定位和加载给定名称的本地库;
  2. Java VM在已经加载了的多个本地库中的其中一个本地库定位到本地实现函数。
    本节介绍另外一种方式实现第二步的工作,不是依赖Java VM去已经加载的本地库搜索本地实现函数,而是JNI编程人员手动通过用class reference, method name,and method descriptor来注册函数指针的办法来链接本地函数。
JNINativeMethod nm;
nm.name = "g";
/* method descriptor assigned to signature field */
nm.signature = "()V";
nm.fnPtr = g_impl;
(*env)->RegisterNatives(env, cls, &nm, 1);

上面的代码注册了本地函数g_impl来作为Foo.g的本地实现。
void JNICALL g_impl(JNIEnv *env, jobject self);
本地函数g_impl既不需要遵循JNI命名转换规则,也不需要用JNIEXPORT从本地库导出,不过仍然需要用JNICALL。RegisterNatives对以下几个目的很有用:

  • 有大量本地实现函数时,用这个方式性能更高;
  • 可以多次调用RegisterNatives,在运行时允许更新本地实现函数;
  • 在一个本地应用嵌入Java VM时,需要链接一个在本地应用的本地实现函数,RegisterNatives特别有用。Java VM这时无法自动搜索本地实现函数,因为它只会搜索本地库,而不会搜索本地应用的函数。

8.4 Load and Unload Handlers

加载和卸载处理程序允许本机库导出两个函数:一个在System.loadLibrary加载本地库时调用,另外一个在Java VM卸载本地库时调用,从Java 2 SDK release 1.2开始添加了这个特性。

8.4.1 The JNI_OnLoad Handler

当System.loadLibrary加载一个本地库,Java VM从本地库搜索下面的入口:
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved);
可以在JNI_Onload内调用任意的JNI函数,JNI_Onload典型的作用是缓存JavaVM指针,class引用,method and field IDs:

JavaVM *cached_jvm;
jclass Class_C;
jmethodID MID_C_g;

NIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
	JNIEnv *env;
	jclass cls;
	cached_jvm = jvm; /* cache the JavaVM pointer */
	if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2)) {
		return JNI_ERR; /* JNI version not supported */
	}
	cls = (*env)->FindClass(env, "C");
	if (cls == NULL) {
		return JNI_ERR;
	}
	/* Use weak global ref to allow C class to be unloaded */
	Class_C = (*env)->NewWeakGlobalRef(env, cls);
	if (Class_C == NULL) {
		return JNI_ERR;
	}
	/* Compute and cache the method ID */
	MID_C_g = (*env)->GetMethodID(env, cls, "g", "()V");
	if (MID_C_g == NULL) {
		return JNI_ERR;
	}
	return JNI_VERSION_1_2;
}

// 获取JNIEnv指针的工具函数
JNIEnv *JNU_GetEnv()
{
	JNIEnv *env;
	(*cached_jvm)->GetEnv(cached_jvm,
	(void **)&env,
	JNI_VERSION_1_2);
	return env;
}

8.4.2 The JNI_OnUnload Handler

直觉上,Java VM在卸载本地库时调用JNI_OnUnload处理器,然而,这还不够精确。VM决定什么时候卸载本地库呢?哪个线程调用JNI_OnUnload处理器呢?规则如下:

  • 某个类C调用System.loadLibrary,C被类加载器L加载,Java VM将L与本地库关联起来;
  • Java VM在某时刻决定类加载器L不再是一个活动对象时,就调用JNI_OnUnload和卸载本地库,因为一个类加载器涉及到所有它加载的类,这意味着类C也会被卸载;
  • JNI_OnUnload处理器在清理器finalizer中运行,要么同步被java.lang.System.runFinalization调用,要么异步被Java VM调用。
JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *jvm, void *reserved)
{
	JNIEnv *env;
	if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2)) {
	return;
	}
	(*env)->DeleteWeakGlobalRef(env, Class_C);
	return;
}

JNI_OnUnload删除在JNI_OnLoad定义的弱全局引用,我们不需要删除MID_C_g,因为卸载类C时会自动回收。
这里解释下为什么用弱全局引用来缓存类C(Class_C),而不用全局引用。全局引用会保持C一直存活,相应地保持了它的类加载器也存活,这样JNI_OnUnload就不可能被调用了。
JNI_OnUnload处理器在清理器finalizer中运行,因为它在一个未知的线程上下文运行,为了避免潜在的死锁问题,在JNI_OnUnload中应当避免复杂的同步和锁定操作,典型的是做一些简单任务比如清理资源的操作。
在JNI_OnUnload中不能再使用类相关的东西,比如Class_C等,因为类被卸载了,所有类定义的东西都不再存在。

8.5 Reflection Support

反射Reflection通常意味着在运行时操纵语言层的构造函数,例如,反射允许你在运行时得到任意对象的名称还有它成员和方法。Java语言提供了java.lang.reflect,还有一些方法定义在java.lang.Object, java.lang.Class, 尽管可以调用Java API的方法,JNI也提供了下面的函数,使常用的反射操作更方便快捷:

  • GetSuperclass返回一个给定类的父类
  • IsAssignableFrom检查一个 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口
  • GetObjectClass返回给定的jobject对应的class
  • IsInstanceOf检查一个jobject是否给定class的实例
  • FromReflectedField, ToReflectedField允许本地代码转换filed IDS到java.lang.reflect.Field对象
  • FromReflectedMethod,ToReflectedMethod允许本地代码转换method IDs到java.lang.reflect.Method对象,和java.lang.reflect.Constructor对象。

猜你喜欢

转载自blog.csdn.net/kingdam578/article/details/83352688
今日推荐