chap 20:使用 Java Native Interface 的最佳实践

谢洛城
2023-12-01

JNI 的发展

JNI 自从 JDK 1.1发行版以来一直是 Java平台的一部分,并且在 JDK 1.2发行版中得到了扩展。JDK 1.0发行版包含一个早期的本机方法接口,但是未明确分隔本机代码和 Java代码。在这个接口中,本机代码可以直接进入 JVM结构,因此无法跨 JVM实现、平台或者甚至各种 JDK版本进行移植。使用 JDK 1.0模型升级含有大量本机代码的应用程序,以及开发能支持多个 JVM实现的本机代码的开销是极高的。

JDK 1.1 中引入的 JNI 支持:

·  版本独立性

·  平台独立性

·  VM独立性

·  开发第三方类库

有一个有趣的地方值得注意,一些较年轻的语言(如 PHP)在它们的本机代码支持方面仍然在努力克服这些问题。

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

2009 7 27

Java™ 本机接口(Java Native InterfaceJNI)是一个标准的 Java API,它支持将 Java 代码与使用其他编程语言编写的代码相集成。如果您希望利用已有的代码资源,那么可以使用 JNI作为您工具包中的关键组件 ——比如在面向服务架构(SOA)和基于云的系统中。但是,如果在使用时未注意某些事项,则 JNI会迅速导致应用程序性能低下且不稳定。本文将确定 10 JNI 编程缺陷,提供避免这些缺陷的最佳实践,并介绍可用于实现这些实践的工具。

Java 环境和语言对于应用程序开发来说是非常安全和高效的。但是,一些应用程序却需要执行纯 Java程序无法完成的一些任务,比如:

·  与旧有代码集成,避免重新编写。

·  实现可用类库中所缺少的功能。举例来说,在 Java语言中实现ping时,您可能需要 Internet Control Message Protocol (ICMP)功能,但基本类库并未提供它。

·  最好与使用 C/C++编写的代码集成,以充分发掘性能或其他与环境相关的系统特性。

·  解决需要非 Java代码的特殊情况。举例来说,核心类库的实现可能需要跨包调用或者需要绕过其他 Java安全性检查。

JNI 允许您完成这些任务。它明确分开了 Java代码与本机代码(C/C++)的执行,定义了一个清晰的 API在这两者之间进行通信。从很大程度上说,它避免了本机代码对 JVM的直接内存引用,从而确保本机代码只需编写一次,并且可以跨不同的 JVM实现或版本运行。

借助 JNI,本机代码可以随意与 Java对象交互,获取和设计字段值,以及调用方法,而不会像 Java代码中的相同功能那样受到诸多限制。这种自由是一把双刃剑:它牺牲 Java代码的安全性,换取了完成上述所列任务的能力。在您的应用程序中使用 JNI提供了强大的、对机器资源(内存、I/O等)的低级访问,因此您不会像普通 Java开发人员那样受到安全网的保护。JNI的灵活性和强大性带来了一些编程实践上的风险,比如导致性能较差、出现 bug甚至程序崩溃。您必须格外留意应用程序中的代码,并使用良好的实践来保障应用程序的总体完整性。

本文介绍 JNI用户最常遇到的 10 大编码和设计错误。其目标是帮助您认识到并避免它们,以便您可以编写安全、高效、性能出众的 JNI代码。本文还将介绍一些用于在新代码或已有代码中查找这些问题的工具和技巧,并展示如何有效地应用它们。

JNI 编程缺陷可以分为两类:

·  性能:代码能执行所设计的功能,但运行缓慢或者以某种形式拖慢整个程序。

·  正确性:代码有时能正常运行,但不能可靠地提供所需的功能;最坏的情况是造成程序崩溃或挂起。

1.性能缺陷

程序员在使用 JNI时的 5 大性能缺陷如下:

·  不缓存方法 ID、字段 ID 和类

·  触发数组副本

·  回访(Reaching back)而不是传递参数

·  错误认定本机代码与 Java 代码之间的界限

使用大量本地引用,而未通知 JVM

1.1.不缓存方法 ID、字段 ID和类

要访问 Java对象的字段并调用它们的方法,本机代码必须调用FindClass()GetFieldID()GetMethodId()GetStaticMethodID()。对于GetFieldID()GetMethodID()GetStaticMethodID()为特定类返回的 ID 不会在 JVM进程的生存期内发生变化。但是,获取字段或方法的调用有时会需要在 JVM中完成大量工作,因为字段和方法可能是从超类中继承而来的,这会让 JVM向上遍历类层次结构来找到它们。由于 ID对于特定类是相同的,因此您只需要查找一次,然后便可重复使用。同样,查找类对象的开销也很大,因此也应该缓存它们。

举例来说,清单 1展示了调用静态方法所需的 JNI代码:



清单 1.使用 JNI 调用静态方法


int val=1;

jmethodID method;

jclass cls;

 

cls = (*env)->FindClass(env,"com/ibm/example/TestClass");

if ((*env)->ExceptionCheck(env)) {

   return ERR_FIND_CLASS_FAILED;

}

method = (*env)->GetStaticMethodID(env,cls, "setInfo", "(I)V");

if ((*env)->ExceptionCheck(env)) {

   returnERR_GET_STATIC_METHOD_FAILED;

}

(*env)->CallStaticVoidMethod(env, cls,method,val);

if ((*env)->ExceptionCheck(env)) {

   returnERR_CALL_STATIC_METHOD_FAILED;

}



 类似资料: