Reflection and Private Parts
The setAccessible(boolean)
method of the
java.lang.reflect.AccessibleObject
class
allows a program to disable language-defined access rules for
specific constructors, methods, or fields. Once the access
checks are disabled, any code can use the
java.lang.reflect.Constructor
,
java.lang.reflect.Method
, or
java.lang.reflect.Field
object to access the
underlying Java entity, without further permission checks. This
breaks encapsulation and can undermine the stability of the
virtual machine. (In contrast, without using the
setAccessible(boolean)
method, this should
not happen because all the language-defined checks still apply.)
This feature should be avoided if possible.
Java Native Interface (JNI)
The Java Native Interface allows calling from Java code
functions specifically written for this purpose, usually in C or
C++.
The transition between the Java world and the C world is not
fully type-checked, and the C code can easily break the Java
virtual machine semantics. Therefore, extra care is needed when
using this functionality.
To provide a moderate amount of type safety, it is recommended
to recreate the class-specific header file using
javah during the build process,
include it in the implementation, and use the
-Wmissing-declarations
option.
Ideally, the required data is directly passed to static JNI
methods and returned from them, and the code and the C side does
not have to deal with accessing Java fields (or even methods).
When using GetPrimitiveArrayCritical
or
GetStringCritical
, make sure that you only
perform very little processing between the get and release
operations. Do not access the file system or the network, and
not perform locking, because that might introduce blocking.
When processing large strings or arrays, consider splitting the
computation into multiple sub-chunks, so that you do not prevent
the JVM from reaching a safepoint for extended periods of time.
If necessary, you can use the Java long
type
to store a C pointer in a field of a Java class. On the C side,
when casting between the jlong
value and the
pointer on the C side,
You should not try to perform pointer arithmetic on the Java
side (that is, you should treat pointer-carrying
long
values as opaque). When passing a slice
of an array to the native code, follow the Java convention and
pass it as the base array, the integer offset of the start of
the slice, and the integer length of the slice. On the native
side, check the offset/length combination against the actual
array length, and use the offset to compute the pointer to the
beginning of the array.
Example 4. Array length checking in JNI code
JNIEXPORT jint JNICALL Java_sum
(JNIEnv *jEnv, jclass clazz, jbyteArray buffer, jint offset, jint length)
{
assert(sizeof(jint) == sizeof(unsigned));
if (offset < 0 || length < 0) {
(*jEnv)->ThrowNew(jEnv, arrayIndexOutOfBoundsExceptionClass,
"negative offset/length");
return 0;
}
unsigned uoffset = offset;
unsigned ulength = length;
// This cannot overflow because of the check above.
unsigned totallength = uoffset + ulength;
unsigned actuallength = (*jEnv)->GetArrayLength(jEnv, buffer);
if (totallength > actuallength) {
(*jEnv)->ThrowNew(jEnv, arrayIndexOutOfBoundsExceptionClass,
"offset + length too large");
return 0;
}
unsigned char *ptr = (*jEnv)->GetPrimitiveArrayCritical(jEnv, buffer, 0);
if (ptr == NULL) {
return 0;
}
unsigned long long sum = 0;
for (unsigned char *p = ptr + uoffset, *end = p + ulength; p != end; ++p) {
sum += *p;
}
(*jEnv)->ReleasePrimitiveArrayCritical(jEnv, buffer, ptr, 0);
return sum;
}
In any case, classes referring to native resources must be
declared final
, and must not be serializeable
or cloneable. Initialization and mutation of the state used by
the native side must be controlled carefully. Otherwise, it
might be possible to create an object with inconsistent native
state which results in a crash (or worse) when used (or perhaps
only finalized) later. If you need both Java inheritance and
native resources, you should consider moving the native state to
a separate class, and only keep a reference to objects of that
class. This way, cloning and serialization issues can be
avoided in most cases.
If there are native resources associated with an object, the
class should have an explicit resource deallocation method
(Resource Management) and a
finalizer (Finalizers) as a
last resort. The need for finalization means that a minimum
amount of synchronization is needed. Code on the native side
should check that the object is not in a closed/freed state.
Many JNI functions create local references. By default, these
persist until the JNI-implemented method returns. If you create
many such references (e.g., in a loop), you may have to free
them using DeleteLocalRef
, or start using
PushLocalFrame
and
PopLocalFrame
. Global references must be
deallocated with DeleteGlobalRef
, otherwise
there will be a memory leak, just as with
malloc
and free
.
When throwing exceptions using Throw
or
ThrowNew
, be aware that these functions
return regularly. You have to return control manually to the
JVM.
Technically, the JNIEnv
pointer is not
necessarily constant during the lifetime of your JNI module.
Storing it in a global variable is therefore incorrect.
Particularly if you are dealing with callbacks, you may have to
store the pointer in a thread-local variable (defined with
__thread
). It is, however, best to avoid the
complexity of calling back into Java code.
Keep in mind that C/C and Java are different languages,
despite very similar syntax for expressions. The Java memory
model is much more strict than the C or C memory models, and
native code needs more synchronization, usually using JVM
facilities or POSIX threads mutexes. Integer overflow in Java
is defined, but in C/C++ it is not (for the
jint
and jlong
types).