defensive-coding-guide/en-US/Java-LowLevel.adoc
2018-02-01 15:47:45 +01:00

141 lines
5.5 KiB
Text

:experimental:
[[sect-Defensive_Coding-Java-LowLevel]]
==== Low-level Features of the Virtual Machine
[[sect-Defensive_Coding-Java-Reflection]]
===== 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.
[[sect-Defensive_Coding-Java-JNI]]
===== 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
[application]*javah* during the build process,
include it in the implementation, and use the
[option]`-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.
[[ex-Defensive_Coding-Java-JNI-Pointers]]
.Array length checking in JNI code
====
[source,java]
----
include::snippets/Java-JNI-Pointers.adoc[]
----
====
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
(<<sect-Defensive_Coding-Java-Language-Resources>>) and a
finalizer (<<sect-Defensive_Coding-Java-Language-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).
[[sect-Defensive_Coding-Java-MiscUnsafe]]
===== `sun.misc.Unsafe`
The `sun.misc.Unsafe` class is unportable and
contains many functions explicitly designed to break Java memory
safety (for performance and debugging). If possible, avoid
using this class.