没有合适的资源?快使用搜索试试~ 我知道了~
Best practices for using JNI
需积分: 10 13 下载量 54 浏览量
2016-03-15
13:43:23
上传
评论
收藏 411KB PDF 举报
温馨提示
试读
22页
Best practices for using the Java Native Interface Techniques and tools for averting the 10 most common JNI programming mistakes.
资源推荐
资源详情
资源评论
© Copyright IBM Corporation 2009 Trademarks
Best practices for using the Java Native Interface Page 1 of 22
Best practices for using the Java Native Interface
Techniques and tools for averting the 10 most common JNI
programming mistakes
Michael Dawson
Advisory Software Developer
IBM
Graeme Johnson
J9 Virtual Machine Development Manager
IBM
Andrew Low
STSM, J9 Virtual Machine
IBM
07 July 2009
The Java™ Native Interface (JNI) is a standard Java API that enables Java code to integrate
with code written in other programming languages. JNI can be a key element in your toolkit if
you want to leverage existing code assets — for example, in a service-oriented architecture
(SOA) or a cloud-based system. But when used without due care, JNI can quickly lead to poorly
performing and unstable applications. This article identifies the top 10 JNI programming pitfalls,
provides best practices for avoiding them, and introduces the tools available for implementing
these practices.
The Java environment and language are safe and efficient for application development. However,
some applications need to perform tasks that go beyond what can be done from within a pure-Java
program, such as:
JNI's evolution
JNI has been part of the Java platform since the JDK 1.1 release and was extended in
the JDK 1.2 release. The JDK 1.0 release included an earlier native-method interface that
lacked clean separation between native and Java code. In this interface, natives would
reach directly into JVM structures and so could not be portable across JVM implementations,
platforms, or even versions of the JDK. Upgrading an application with a substantial number
of natives using the JDK 1.0 model was expensive, as was developing natives that could run
with multiple JVM implementations.
developerWorks® ibm.com/developerWorks/
Best practices for using the Java Native Interface Page 2 of 22
The introduction of JNI in the JDK 1.1 release allowed:
• Version independence
• Platform independence
• VM independence
• Development of third-party class libraries
It is interesting to note that younger languages such as PHP are still struggling with these
issues with respect to their support of native code.
• Integrate with existing legacy code to avoid a rewrite.
• Implement functionality missing in available class libraries. For example, you might need
Internet Control Message Protocol (ICMP) functionality if you are implementing ping in the
Java language, but the base class libraries don't provide it.
• Integrate with code that's best written in C/C++, to exploit performance or other environment-
specific system characteristics.
• Address special circumstances that require non-Java code. For example, implementation
of core class libraries might require cross-package calls or the need to bypass other Java
security checks.
The JNI lets you accomplish these tasks. It provides a clean separation between the execution of
Java code and native code (such as C/C++) by defining a clear API for communicating between
the two. For the most part, it avoids direct memory reference by native code into the JVM, ensuring
that natives can be written once and work across different JVM implementations or versions.
With JNI, native code is free to interact with Java objects, get and set field values, and invoke
methods without many of the constraints that apply to the same functions in Java code. This
freedom is a double-edged sword: it trades the safety of the Java language for the ability to
accomplish the tasks listed earlier. Using JNI within your application provides powerful low-
level access to the machine resources (memory, I/O, and so on), so you're working without the
safety net usually provided to Java developers. JNI's flexibility and power introduce the risk of
programming practices that can lead to poor performance, bugs, and even program crashes.
You must be careful about the code you include in your application and use good practices to
safeguard the application's overall integrity.
Develop skills on this topic
This content is part of a progressive knowledge path for advancing your skills. See Using the
Java Native Interface
This article covers the 10 most common coding and design errors that users of the JNI make. The
goal is to help you recognize and steer clear of them so that you can write safe, effective JNI code
that performs well from the start. This article also introduces available tools and techniques for
finding these issues in new or existing code and show how to apply them effectively.
JNI programming pitfalls fall into two categories:
• Performance: The code performs the designed function but does so slowly or in a way that
causes the overall program to slow down.
ibm.com/developerWorks/ developerWorks®
Best practices for using the Java Native Interface Page 3 of 22
• Correctness: The code works some of the time but does not reliably provide the required
function; in the worst case, it crashes or hangs.
Performance pitfalls
The top five performance pitfalls for programmers using JNI are:
• Not caching method IDs, field IDs, and classes
• Triggering array copies
• Reaching back instead of passing parameters
• Choosing the wrong boundary between native and Java code
• Using many local references without informing the JVM
Not caching method IDs, field IDs, and classes
To access Java objects' fields and invoke their methods, native code must make calls to
FindClass(), GetFieldID(), GetMethodId(), and GetStaticMethodID(). In the case of
GetFieldID(), GetMethodID(), and GetStaticMethodID(), the IDs returned for a given class
don't change for the lifetime of the JVM process. But the call to get the field or method can
require significant work in the JVM, because fields and methods might have been inherited from
superclasses, making the JVM walk up the class hierarchy to find them. Because the IDs are the
same for a given class, you should look them up once and then reuse them. Similarly, looking up
class objects can be expensive, so they should be cached as well.
For example, Listing 1 shows the JNI code required to call a static method:
Listing 1. Calling a static method with 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)) {
return ERR_GET_STATIC_METHOD_FAILED;
}
(*env)->CallStaticVoidMethod(env, cls, method,val);
if ((*env)->ExceptionCheck(env)) {
return ERR_CALL_STATIC_METHOD_FAILED;
}
Looking up the class and method ID every time we want to call the method results in six native
calls instead of the two that would be required if we had cached the class and method ID the first
time they were needed.
Caching makes a significant impact on your application's run time. Consider the following two
versions of a method, which end up doing the same thing. The version in Listing 2 uses cached
field IDs:
developerWorks® ibm.com/developerWorks/
Best practices for using the Java Native Interface Page 4 of 22
Listing 2. Using cached field IDs
int sumValues2(JNIEnv* env, jobject obj, jobject allValues){
jint avalue = (*env)->GetIntField(env, allValues, a);
jint bvalue = (*env)->GetIntField(env, allValues, b);
jint cvalue = (*env)->GetIntField(env, allValues, c);
jint dvalue = (*env)->GetIntField(env, allValues, d);
jint evalue = (*env)->GetIntField(env, allValues, e);
jint fvalue = (*env)->GetIntField(env, allValues, f);
return avalue + bvalue + cvalue + dvalue + evalue + fvalue;
}
Performance Tip #1
Look up and globally cache commonly used classes, field IDs, and method IDs.
Listing 3 doesn't use cached field IDs:
Listing 3. Field IDs not cached
int sumValues2(JNIEnv* env, jobject obj, jobject allValues){
jclass cls = (*env)->GetObjectClass(env,allValues);
jfieldID a = (*env)->GetFieldID(env, cls, "a", "I");
jfieldID b = (*env)->GetFieldID(env, cls, "b", "I");
jfieldID c = (*env)->GetFieldID(env, cls, "c", "I");
jfieldID d = (*env)->GetFieldID(env, cls, "d", "I");
jfieldID e = (*env)->GetFieldID(env, cls, "e", "I");
jfieldID f = (*env)->GetFieldID(env, cls, "f", "I");
jint avalue = (*env)->GetIntField(env, allValues, a);
jint bvalue = (*env)->GetIntField(env, allValues, b);
jint cvalue = (*env)->GetIntField(env, allValues, c);
jint dvalue = (*env)->GetIntField(env, allValues, d);
jint evalue = (*env)->GetIntField(env, allValues, e);
jint fvalue = (*env)->GetIntField(env, allValues, f);
return avalue + bvalue + cvalue + dvalue + evalue + fvalue
}
The version in Listing 2 takes 3,572 ms to run 10,000,000 times. Listing 3's version takes 86,217
ms — 24 times longer.
Triggering array copies
JNI provides a clean interface between Java code and native code. To maintain this separation,
arrays are passed as opaque handles, and native code must call back to the JVM in order to
manipulate array elements using set and get calls. The Java specification leaves it up to the JVM
implementation whether these calls provide direct access to the arrays or return a copy of the
array. For example, the JVM might return a copy when it has optimized arrays in a way that does
not store them contiguously. (See Resources for a description of one such JVM.)
These calls, then, might cause copying of the elements being manipulated. For example, if you
call GetLongArrayElements() on an array with 1,000 elements, you might cause the allocation
and copy of at least 8,000 bytes (1,000 elements * 8 bytes for each long). When you then update
the array's contents with ReleaseLongArrayElements(), another copy of 8,000 bytes might be
ibm.com/developerWorks/ developerWorks®
Best practices for using the Java Native Interface Page 5 of 22
required to update the array. Even when you use the newer GetPrimitiveArrayCritical(), the
specification still permits the JVM to make copies of the full array.
Performance Tip #2
Get and update only those parts of an array that the native needs. Use the appropriate API
calls to avoid copying the whole array when only part of it is needed.
The GetTypeArrayRegion() and SetTypeArrayRegion() methods allow you to get and update a
region of an array, as opposed to the full array. By using these methods to access larger arrays,
you can ensure that you copy only the region of the array that the native will actually use.
For example, consider two versions of the same method, shown in Listing 4:
Listing 4. Two versions of the same method
jlong getElement(JNIEnv* env, jobject obj, jlongArray arr_j,
int element){
jboolean isCopy;
jlong result;
jlong* buffer_j = (*env)->GetLongArrayElements(env, arr_j, &isCopy);
result = buffer_j[element];
(*env)->ReleaseLongArrayElements(env, arr_j, buffer_j, 0);
return result;
}
jlong getElement2(JNIEnv* env, jobject obj, jlongArray arr_j,
int element){
jlong result;
(*env)->GetLongArrayRegion(env, arr_j, element,1, &result);
return result;
}
The first version might cause two full copies of the array, whereas the second causes no copying
at all. Running the first method 10,000,000 times with an array of 1,000 bytes takes 12,055 ms; the
second version takes only 1,421 ms. The first version takes 8.5 times longer!
Performance Tip #3
Get or update as much of an array as possible in a single API call. Don't iterate through the
array's elements one by one when you can get and update an array in larger blocks.
On the other hand, using GetTypeArrayRegion() to get each of the array's elements one by one
will also not perform well if you're going to end up getting all of the array's elements anyway. For
best performance, ensure that you get and update array elements in the largest sensible blocks.
If you're going to iterate through all of the elements in an array, neither of the two getElement()
methods in Listing 4 is suitable. Instead, you'd want to get a reasonable-sized chunk of the array in
one call and then iterate through all of those elements, repeating until you cover the full array.
Reaching back instead of passing parameters
When calling a method, you can often choose between passing a single object that has multiple
fields and passing the fields individually. With object-oriented designs, passing the object often
provides better encapsulation, in that changes in the object fields don't require changes to the
剩余21页未读,继续阅读
资源评论
sunrain_hjb
- 粉丝: 370
- 资源: 223
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功