JNI specification in its current shape isn't clear enough about when class
initialization checks are performed. It led to the situation when current
implementation significantly diverges from bytecode behavior (mandated by JVMS)
which cause problems both from user and implementation perspectives.
From a user perspective, it's hard to write correct code in presence of recursive initialization, multi-threaded accesses, and possibility of initialization failure. The only way to check that current thread is the initializing thread or notice a failure using JNI is to repeat jmethodID/jfieldID resolution. And bugs lead to accesses into partially initialized classes or classes in error state.
From implementation perspective, difference between JNI & bytecode behavior complicates implementation in the JVM.
Some details:
JVMS-5.5 enumerates all the scenarios when class initialization happens. Among them:
* The execution of any one of the Java Virtual Machine instructions new,
getstatic, putstatic, or invokestatic that references C (§new, §getstatic,
§putstatic, §invokestatic).
* Upon execution of a new instruction, the class to be initialized is the class referenced by the instruction.
* Upon execution of a getstatic, putstatic, or invokestatic instruction, the
class or interface to be initialized is the class or interface that declares
the resolved field or method.
JNI specification:
GetStaticFieldID
GetStaticFieldID() causes an uninitialized class to be initialized.
THROWS: ExceptionInInitializerError: if the class initializer fails due to an exception.
GetStatic<type>Field Routines
SetStatic<type>Field Routines
Oblivious of class initialization.
GetFieldID
GetFieldID() causes an uninitialized class to be initialized.
THROWS: ExceptionInInitializerError: if the class initializer fails due to an exception.
GetStaticMethodID
GetStaticMethodID() causes an uninitialized class to be initialized.
THROWS: ExceptionInInitializerError: if the class initializer fails due to an exception.
CallStatic<type>Method Routines
CallStatic<type>MethodA Routines
CallStatic<type>MethodV Routines
Oblivious of class initialization.
GetMethodID
GetMethodID() causes an uninitialized class to be initialized.
THROWS: ExceptionInInitializerError: if the class initializer fails due to an exception.
AllocObject
Oblivious of class initialization.
NewObject, NewObjectA, NewObjectV
Oblivious of class initialization.
Current implementation behavior:
* GetStaticFieldID and GetStaticMethodID perform class initialization check
* (Get|Set)Static<type>Field and CallStatic<type>Method* don't have the check
* AllocObject and NewObject* perform the check
Update:
The problem is more extensive than just initialization, the specification is also unclear when it comes to the different steps of loading and linking a class prior to initialization. So methods have to allow for LinkageErrors as well as those pertaining to class initialization. Basically any method that takes a reference to a Class instance should state what affect it has on the linking and initialization state of that class.
Also note that it is possible for instances of a class to escape even if a class fails initialization and is marked as erroneous. These instances can then be operated on, and the result of that must consistent with what happens at the Java or bytecode level. There is a risk that we do not obey the basic exception rule that "In all other cases, a non-error return value guarantees that no exceptions have been thrown."
initialization checks are performed. It led to the situation when current
implementation significantly diverges from bytecode behavior (mandated by JVMS)
which cause problems both from user and implementation perspectives.
From a user perspective, it's hard to write correct code in presence of recursive initialization, multi-threaded accesses, and possibility of initialization failure. The only way to check that current thread is the initializing thread or notice a failure using JNI is to repeat jmethodID/jfieldID resolution. And bugs lead to accesses into partially initialized classes or classes in error state.
From implementation perspective, difference between JNI & bytecode behavior complicates implementation in the JVM.
Some details:
JVMS-5.5 enumerates all the scenarios when class initialization happens. Among them:
* The execution of any one of the Java Virtual Machine instructions new,
getstatic, putstatic, or invokestatic that references C (§new, §getstatic,
§putstatic, §invokestatic).
* Upon execution of a new instruction, the class to be initialized is the class referenced by the instruction.
* Upon execution of a getstatic, putstatic, or invokestatic instruction, the
class or interface to be initialized is the class or interface that declares
the resolved field or method.
JNI specification:
GetStaticFieldID
GetStaticFieldID() causes an uninitialized class to be initialized.
THROWS: ExceptionInInitializerError: if the class initializer fails due to an exception.
GetStatic<type>Field Routines
SetStatic<type>Field Routines
Oblivious of class initialization.
GetFieldID
GetFieldID() causes an uninitialized class to be initialized.
THROWS: ExceptionInInitializerError: if the class initializer fails due to an exception.
GetStaticMethodID
GetStaticMethodID() causes an uninitialized class to be initialized.
THROWS: ExceptionInInitializerError: if the class initializer fails due to an exception.
CallStatic<type>Method Routines
CallStatic<type>MethodA Routines
CallStatic<type>MethodV Routines
Oblivious of class initialization.
GetMethodID
GetMethodID() causes an uninitialized class to be initialized.
THROWS: ExceptionInInitializerError: if the class initializer fails due to an exception.
AllocObject
Oblivious of class initialization.
NewObject, NewObjectA, NewObjectV
Oblivious of class initialization.
Current implementation behavior:
* GetStaticFieldID and GetStaticMethodID perform class initialization check
* (Get|Set)Static<type>Field and CallStatic<type>Method* don't have the check
* AllocObject and NewObject* perform the check
Update:
The problem is more extensive than just initialization, the specification is also unclear when it comes to the different steps of loading and linking a class prior to initialization. So methods have to allow for LinkageErrors as well as those pertaining to class initialization. Basically any method that takes a reference to a Class instance should state what affect it has on the linking and initialization state of that class.
Also note that it is possible for instances of a class to escape even if a class fails initialization and is marked as erroneous. These instances can then be operated on, and the result of that must consistent with what happens at the Java or bytecode level. There is a risk that we do not obey the basic exception rule that "In all other cases, a non-error return value guarantees that no exceptions have been thrown."