a number of mistakes commonly made by JNI programmers.
2012-10-29 15:48
281 查看
Chapter 10
Traps and Pitfalls
To highlight the important techniques covered in previous chapters, this chapter covers a number of mistakes commonly made by JNI programmers. Each mistake described here has occurred in real-worldprojects.
10.1 Error Checking
The most common mistake when writing native methods is forgetting to check whether an error condition has occurred. Unlike the Java programming language, native languages do not offer standardexception mechanisms. The JNI does not rely on any particular native exception mechanism (such as C++ exceptions). As a result, programmers are required to perform explicit
checks after every JNI function call that could possibly raise an exception. Not all JNI functions raise exceptions, but most can. Exception checks are tedious, but are necessary to ensure that the application using native methods is robust.
The tediousness of error checking greatly emphasizes the need to limit native code to those well-defined subsets of an application where it is necessary to use the JNI (§10.5).
10.2 Passing Invalid Arguments to JNI Functions
The JNI functions do not attempt to detect or recover from invalid arguments. If you passNULLor
(jobject)0xFFFFFFFFto a JNI function that expects a reference, the
resulting behavior is undefined. In practice this could either lead to incorrect results or virtual machine crashes. Java 2 SDK release 1.2 provides you with a command-line option
-Xcheck:jni. This option instructs the virtual machine to detect
and report many, though not all, cases of native code passing illegal arguments to JNI functions. Checking the validity of arguments incurs a significant amount of overhead and thus is not enabled by default.
Not checking the validity of arguments is a common practice in C and C++ libraries. Code that uses the library is responsible for making sure that all the arguments passed to library functions
are valid. If, however, you are used to the Java programming language, you may have to adjust to this particular aspect of the lack of safety in JNI programming.
10.3 Confusing jclass with jobject
The differences between instance references (a value of thejobjecttype) and class references (a value of the
jclasstype) can be confusing when first using the JNI.
Instance references correspond to arrays and instances of
java.lang.Objector one of its subclasses. Class references correspond to
java.lang.Classinstances, which
represent class types.
An operation such as
GetFieldID, which takes a
jclass, is a class operation because
it gets the field descriptor from a class. In contrast,
GetIntField, which takes a
jobject, is an instance operation because it gets the value of a field from an instance. The association of
jobjectwith instance operations
and the association of
jclasswith class operations are consistent across all JNI functions, so it is easy to remember that class operations are distinct from instance operations.
10.4 Truncating jboolean Arguments
Ajbooleanis an 8-bit unsigned C type that can store values from 0 to 255. The value 0 corresponds to the constant
JNI_FALSE, and the values from 1 to 255 correspond
to
JNI_TRUE. But 32-bit or 16-bit values greater than 255 whose lower 8 bits are 0 pose a problem.
Suppose you have defined a function
conditionwhose type is
jboolean:
void print(jboolean condition) { /* C compilers generate code that truncates condition to its lower 8 bits. */ if (condition) { printf("true\n"); } else { printf("false\n"); } }
There is nothing wrong with the previous definition. However, the following innocent-looking call to
int n = 256; /* the value 0x100, whose lower 8 bits are all 0 */ print(n);
We passed a non-zero value (256) to
prints "
false," contrary to expectations.
A good rule of thumb when coercing integral types, such as
int, to
jbooleanis always to evaluate conditions on the integral type, thereby avoiding inadvertent errors
during coercion. You can rewrite the call to
n = 256; print (n ? JNI_TRUE : JNI_FALSE);
10.5 Boundaries between Java Application and Native Code
A common question when designing a Java application supported by native code is "What, and how much, should be in native code?" The boundaries between the native code and the rest of the applicationwritten in the Java programming language are application-specific, but there are some generally applicable principles:
Keep the boundaries simple. Complex control flow that goes back and forth between the Java virtual machine and native code can be hard to debug and maintain. Such control flow also gets in the way of optimizations performed by high-performance
virtual machine implementations. For example, it is much easier for a virtual machine implementation to inline methods defined in the Java programming language than to inline native methods defined in C and C++.
Keep the code on the native code side minimal. There are compelling reasons to do so. Native code is neither portable nor type-safe. Error checking in native code is tedious (§10.1).
It is good software engineering to keep such parts to a minimum.
Keep native code isolated. In practice, this could mean that all native methods are in the same package or in the same class, isolated from the rest of the application. The package or the class containing native methods essentially becomes the
"porting layer" for the application.
The JNI provides access to virtual machine functionality such as class loading, object creation, field access, method calls, thread synchronization, and so forth. It is sometimes tempting to
express complex interactions with Java virtual machine functionality in native code, when in fact it is simpler to accomplish the same task in the Java programming language. The following example shows why "Java programming in native code" is bad practice.
Consider a simple statement that creates a new thread written in the Java programming language:
[code]new JobThread().start();
The same statement can also be written in native code:
[code]/* Assume these variables are precomputed and cached:
* Class_JobThread: the class "JobThread"
* MID_Thread_init: method ID of constructor
* MID_Thread_start: method ID of Thread.start()
*/aThreadObject =
(*env)->NewObject(env, Class_JobThread, MID_Thread_init);
if (aThreadObject == NULL) {
... /* out of memory*/}
(*env)->CallVoidMethod(env, aThreadObject, MID_Thread_start);
if ((*env)->ExceptionOccurred(env)) {
... /* thread did not start*/}
[/code]
The native code is much more complex than its equivalent written in the Java programming language despite the fact that we have omitted the lines of code needed for error checks.
Rather than writing a complex segment of native code manipulating the Java virtual machine, it is often preferable to define an auxiliary method in the Java programming language and have the
native code issue a callback to the auxiliary method.
10.6 Confusing IDs with References
The JNI exposes objects as references. Classes, strings, and arrays are special types of references. The JNI exposes methods and fields as IDs. An ID is not a reference. Do not call a class referencea "class ID" or a method ID a "method reference."
References are virtual machine resources that can be managed explicitly by native code. The JNI function
DeleteLocalRef, for example, allows native code to delete a local reference.
In contrast, field and method IDs are managed by the virtual machine and remain valid until their defining class is unloaded. Native code cannot explicitly delete a field or method ID before the the virtual machine unloads the defining class.
Native code may create multiple references that refer to the same object. A global and a local reference, for example, may refer to the same object. In contrast, a unique field or method ID is
derived for the same definition of a field or a method. If class
Adefines method
fand class
Binherits
ffrom
A, the two
GetMethodIDcalls in the following code always
return the same result:
jmethodID MID_A_f = (*env)->GetMethodID(env, A, "f", "()V"); jmethodID MID_B_f = (*env)->GetMethodID(env, B, "f", "()V");
10.7 Caching Field and Method IDs
Native code obtains field or method IDs from the virtual machine by specifying the name and type descriptor of the field or method as strings (§4.1,§4.2).Field and method lookups using name and type strings are slow. It often pays off to cache the IDs. Failure to cache field and method IDs is a common performance problem in native code.
In some cases caching IDs is more than a performance gain. A cached ID may be necessary to ensure that the correct field or method is accessed by native code. The following example illustrates
how the failure to cache a field ID can lead to a subtle bug:
class C { private int i; native void f(); }
Suppose that the native method
fneeds to obtain the value of the field
iin an instance of
C. A straightforward implementation that does not cache an ID
accomplishes this in three steps: 1) get the class of the object; 2) look up the field ID for
ifrom the class reference; and 3) access the field value based on the object reference and field ID:
// No field IDs cached.
JNIEXPORT void JNICALL
Java_C_f(JNIEnv *env, jobject this) {
jclass cls = (*env)->GetObjectClass(env, this);
... /* error checking*/jfieldID fid = (*env)->GetFieldID(env, cls, "i", "I");
... /* error checking*/ival = (*env)->GetIntField(env, this, fid);
... /* ival now has the value of this.i*/}
The code works fine until we define another class
Das a subclass of
C, and declare a private field also named "
i" in
D:
// Trouble in the absence of ID caching class D extends C { private int i; D() { f(); // inherited from C } }
When
D's constructor calls
C.f, the native method receives an instance of
Das the
thisargument,
clsrefers to the
Dclass,
and
fidrepresents
D.i. At the end of the native method,
ivalcontains the value of
D.i, instead of
C.i. This might not be what you expected when implementing native method
C.f.
The solution is to compute and cache the field ID at the time when you are certain that you have a class reference to
C, not
D. Subsequent accesses from this cached
ID will always refer to the right field
C.i. Here is the corrected version:
// Version that caches IDs in static initializers class C { private int i; native void f(); private static native void initIDs(); static { initIDs(); // Call an initializing native method } }
The modified native code is:
static jfieldID FID_C_i;
JNIEXPORT void JNICALL
Java_C_initIDs(JNIEnv *env, jclass cls) {
/* Get IDs to all fields/methods of C that
native methods will need.*/FID_C_i = (*env)->GetFieldID(env, cls, "i", "I");
}
JNIEXPORT void JNICALL
Java_C_f(JNIEnv *env, jobject this) {
ival = (*env)->GetIntField(env, this, FID_C_i);
... /* ival is always C.i, not D.i*/}
The field ID is computed and cached in
C's static initializer. This guarantees that the field ID for
C.iwill be cached, and thus the native method implementation
Java_C_fwill
read the value of
C.iindependent of the actual class of the
thisobject.
Caching may be needed for some method calls as well. If we change the above example slightly so that classes
Cand
Deach have their own definition of aprivate method
g,
fneeds
to cache the method ID of
C.gto avoid accidentally calling
D.g. Caching is not needed for making correct virtual method calls. Virtual methods by definition dynamically bind to the instance on which the method is invoked. Thus you
can safely use the
JNU_CallMethodByNameutility function (§6.2.3) to call virtual methods. The previous example tells us, however, why we do not define a similar
JNU_GetFieldByNameutility
function.
10.8 Terminating Unicode Strings
Unicode strings obtained fromGetStringCharsor
GetStringCriticalare not
NULL-terminated. Call
GetStringLengthto find out the number of 16-bit
Unicode characters in a string. Some operating systems, such as Windows NT, expect two trailing zero byte values to terminate Unicode strings. You cannot pass the result of
GetStringCharsto Windows NT APIs that expect a Unicode string. You must
make another copy of the string and insert the two trailing zero byte values.
10.9 Violating Access Control Rules
The JNI does not enforce class, field, and method access control restrictions that can be expressed at the Java programming language level through the use of modifiers such asprivateand
final.
It is possible to write native code to access or modify fields of an object even though doing so at the Java programming language level would lead to an
IllegalAccessException. JNI's permissiveness was a conscious design decision, given that native
code can access and modify any memory location in the heap anyway.
Native code that bypasses source-language-level access checks may have undesirable effects on program execution. For example, an inconsistency may be created if a native method modifies a
finalfield
after a just-in-time (JIT) compiler has inlined accesses to the field. Similarly, native methods should not modify immutable objects such as fields in instances of
java.lang.Stringor
java.lang.Integer. Doing so may lead to breakage
of invariants in the Java platform implementation.
10.10 Disregarding Internationalization
Strings in the Java virtual machine consist of Unicode characters, whereas native strings are typically in a locale-specific encoding. Use utility functions such asJNU_NewStringNative(§8.2.1)
and
JNU_GetStringNativeChars(§8.2.2) to translate between Unicode
jstringsand locale-specific native strings of the underlying host environment. Pay special
attention to message strings and file names, which typically are internationalized. If a native method gets a file name as a
jstring, the file name must be translated to a native string before being passed to a C library routine.
The following native method,
MyFile.open, opens a file and returns the file descriptor as its result:
JNIEXPORT jint JNICALL Java_MyFile_open(JNIEnv *env, jobject self, jstring name, jint mode) { jint result; char *cname = JNU_GetStringNativeChars(env, name); if (cname == NULL) { return 0; } result = open(cname, mode); free(cname); return result; }
We translate the
jstringargument using the
JNU_GetStringNativeCharsfunction because the
opensystem call expects the file name to be in the locale-specific
encoding.
10.11 Retaining Virtual Machine Resources
A common mistake in native methods is forgetting to free virtual machine resources. Programmers need to be particularly careful in code paths that are only executed when there is an error. Thefollowing code segment, a slight modification of an example in Section 6.2.2, misses a
ReleaseStringCharscall:
JNIEXPORT void JNICALL
Java_pkg_Cls_f(JNIEnv *env, jclass cls, jstring jstr)
{
const jchar *cstr =
(*env)->GetStringChars(env, jstr, NULL);
if (cstr == NULL) {
return;
}
...
if (...) { /* exception occurred*//* misses a ReleaseStringChars call*/return;
}
...
/* normal return*/(*env)->ReleaseStringChars(env, jstr, cstr);
}
Forgetting to call the
ReleaseStringCharsfunction may cause either the
jstringobject to be pinned indefinitely, leading to memory fragmentation, or the C copy to be
retained indefinitely, a memory leak.
There must be a corresponding
ReleaseStringCharscall whether or not
GetStringCharshas made a copy of the string. The following code fails to release virtual machine
resources properly:
/* The isCopy argument is misused here!*/JNIEXPORT void JNICALL
Java_pkg_Cls_f(JNIEnv *env, jclass cls, jstring jstr)
{
jboolean isCopy;
const jchar *cstr = (*env)->GetStringChars(env, jstr,
&isCopy);
if (cstr == NULL) {
return;
}
... /* use cstr*//* This is wrong. Always need to call ReleaseStringChars.*/if (isCopy) {
(*env)->ReleaseStringChars(env, jstr, cstr);
}
}
The call to
ReleaseStringCharsis still needed even when
isCopyis
JNI_FALSEso that the virtual machine will unpin the
jstringelements.
10.12 Excessive Local Reference Creation
Excessive local reference creation causes programs to retain memory unnecessarily. An unnecessary local reference wastes memory both for the referenced object and for the reference itself.Pay special attention to long-running native methods, local references created in loops, and utility functions. Take advantage of the new
Push/PopLocalFramefunctions in Java 2 SDK
release 1.2 to manage local references more effectively. Refer to Section 5.2.1 and Section 5.2.2 for a more detailed
discussion of this problem.
You can specify the
-verbose:jnioption in Java 2 SDK 1.2 to ask the virtual machine to detect and report excessive local reference creation. Suppose that you run a class
Foowith
this option:
% java -verbose:jni Foo
and the output contains the following:
***ALERT: JNI local ref creation exceeded capacity (creating: 17, limit: 16). at Baz.g (Native method) at Bar.f (Compiled method) at Foo.main (Compiled method)
It is likely that the native method implementation for
Baz.gfails to manage local references properly.
10.13 Using Invalid Local References
Local references are valid only inside a single invocation of a native method. Local references created in a native method invocation are freed automatically after the native function that implementsthe method returns. Native code should not store a local reference in a global variable and expect to use it in later invocations of the native method.
Local references are valid only within the thread in which they are created. You should not pass a local reference from one thread to another. Create a global reference when it is necessary to
pass a reference across threads.
10.14 Using the JNIEnv across Threads
TheJNIEnvpointer, passed as the first argument to every native method, can only be used in the thread with which it is associated. It is wrong to cache the
JNIEnvinterface
pointer obtained from one thread, and use that pointer in another thread. Section 8.1.4 explains how you can obtain the
JNIEnvinterface pointer for the current thread.
10.15 Mismatched Thread Models
The JNI works only if the host native code and the Java virtual machine implementation share the same thread model (§8.1.5).For example, programmers cannot attach native platform threads to an embedded Java virtual machine implemented using a user thread package.
On Solaris, Sun ships a virtual machine implementation that is based on a user thread package known as Green threads. If your native code relies on Solaris native thread support, it
will not work with a Green-thread-based Java virtual machine implementation. You need a virtual machine implementation that is designed to work with Solaris native threads. Native threads support in Solaris JDK release 1.1 requires a separate download. The
native threads support is bundled with Solaris Java 2 SDK release 1.2.
Sun's virtual machine implementation on Win32 supports native threads by default, and can be easily embedded into native Win32 applications.
相关文章推荐
- Best Practices and Commonly Made Mistakes When Using jQuery
- type … is table of number index by binary_integer
- 1. CountDiv 数数有几个 Compute number of integers divisible by k in range [a..b].
- A Day Made of Glass by Corning(2010)
- Python: Number of rows affected by cursor.execute("SELECT …)
- Solution of NumberOfDiscIntersections by Codility
- Controlling the number of Partitions in Spark for shuffle transformations (Ex. reduceByKey)
- Common Mistakes Made By Online College Students
- Solution of NumberOfDiscIntersections by Codility
- The Number of Triangles Formed by Intersecting Diagonals of a Regular Polygon
- The Number of Triangles Formed by Intersecting Diagonals of a Regular Polygon
- How to arrange 10 digits so that the product of the some of them is equal to a number represented by the remaining digits
- How to arrange 10 digits so that the product of the some of them is equal to a number represented by the remaining digits
- Caused by: java.sql.SQLException: Parameter index out of range (1 > number of parameters, which is 0
- Change Number to English By Reading rule of money
- An Example of Software Application made by Delphi XE
- Compute modulus division by a power-of-2-number
- ruby merge array of hashes based on the key and order it by number of key/value pair
- Given any integer 0 <= n <= 10000 not divisible by 2 or 5, some multiple of n is a number which in d
- Solution of NumberOfDiscIntersections by Codility