Hi,
can I please have review (and a sponsor) for these changes:
https://bugs.openjdk.java.net/browse/JDK-8081674
http://cr.openjdk.java.net/~simonis/webrevs/2015/8081674.jdk
http://cr.openjdk.java.net/~simonis/webrevs/2015/8081674.hs
Please notice that the fix requires synchronous changes in the
jdk and
the
hotspot forest.
The changes themselves are by far not that big as this
explanation but I
found the problem to be quite intricate so I tried to explain it
as good
as
I could. I'd suggest to read the HTML-formatted explanation in
the webrev
although the content is equal to the one in this mail:
Using an unsupported character encoding (e.g. vi_VN.TCVN on Linux)
results
in an immediate VM failure with jdk 8 and 9:
export LANG=vi_VN.TCVN
java -version
Error occurred during initialization of VM
java.util.EmptyStackException
at java.util.Stack.peek(Stack.java:102)
at
java.lang.ClassLoader$NativeLibrary.getFromClass(ClassLoader.java:1751)
at
java.lang.ClassLoader$NativeLibrary.findBuiltinLib(Native
Method)
at
java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1862)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1835)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1119)
at java.lang.System.initializeSystemClass(System.java:1194)
This is a consequence of "8005716: Enhance JNI specification to
allow
support of static JNI libraries in Embedded JREs".
With jdk 9 we get this error even if we're running with a supported
charset
which is in the ExtendedCharsets (as opposed to being in the
StandardCharsets) class which is a consequence of delegating the
loading
of
the ExtendedCharsets class to the ServiceLoader in jdk 9.
export LANG=eo.iso-8859-3
output-jdk9/images/jdk/bin/java -version
Error occurred during initialization of VM
java.util.EmptyStackException
at java.util.Stack.peek(Stack.java:102)
at
java.lang.ClassLoader$NativeLibrary.getFromClass(ClassLoader.java:1737)
at
java.lang.ClassLoader$NativeLibrary.findBuiltinLib(Native
Method)
at
java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1866)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1840)
at java.lang.Runtime.loadLibrary0(Runtime.java:874)
at java.lang.System.loadLibrary(System.java:1111)
at java.lang.System.initializeSystemClass(System.java:1186)
Here's why the exception happens for an unsupported charset (see the
mixed
stack trace below for the full details):
java.lang.System.loadLibrary() wants to load libzip.so. It calls
java.lang.Runtime.loadLibrary0() which at the very beginning
calls the
native method ClassLoader$NativeLibrary.findBuiltinLib() which
checks if
the
corresponding library is already statically linked into the VM
(introduced
by 8005716).
Java_java_lang_ClassLoader_00024NativeLibrary_findBuiltinLib(),
the native implementation of findBuiltinLib() in Classloader.c calls
GetStringPlatformChars() to convert the library name into the native
platform encoding. GetStringPlatformChars() calls the helper
function
jnuEncodingSupported() to check if the platform encoding which is
stored
in
the property "sun.jnu.encoding" is supported by Java.
jnuEncodingSupported()
is implemented as follows:
static jboolean isJNUEncodingSupported = JNI_FALSE;
static jboolean jnuEncodingSupported(JNIEnv *env) {
jboolean exe;
if (isJNUEncodingSupported == JNI_TRUE) {
return JNI_TRUE;
}
isJNUEncodingSupported = (jboolean)
JNU_CallStaticMethodByName (
env, &exe,
"java/nio/charset/Charset",
"isSupported",
"(Ljava/lang/String;)Z",
jnuEncoding).z;
return isJNUEncodingSupported;
}
Once the function finds that the platform encoding is supported (by
calling
java.nio.charset.Charset.isSupported()) it caches this value and
always
returns it on following calls. However if the platform encoding
is not
supported, it ALWAYS calls java.nio.charset.Charset.isSupported() an
every
subsequent invocation.
In order to call the Java method Charset.isSupported() (in
JNU_CallStaticMethodByName() in file jni_util.c), we have to call
jni_FindClass() to convert the symbolic class name
"java.nio.charset.Charset" into a class reference.
But unfortunately, jni_FindClass() (from jni.cpp in libjvm.so) has a
special
handling if called from java.lang.ClassLoader$NativeLibrary to
ensure
that
JNI_OnLoad/JNI_OnUnload are executed in the correct class context:
instanceKlassHandle k (THREAD,
thread->security_get_caller_class(0));
if (k.not_null()) {
loader = Handle(THREAD, k->class_loader());
// Special handling to make sure JNI_OnLoad and
JNI_OnUnload are
executed
// in the correct class context.
if (loader.is_null() &&
k->name() ==
vmSymbols::java_lang_ClassLoader_NativeLibrary()) {
JavaValue result(T_OBJECT);
JavaCalls::call_static(&result, k,
vmSymbols::getFromClass_name(),
vmSymbols::void_class_signature(),
thread);
if (HAS_PENDING_EXCEPTION) {
Handle ex(thread, thread->pending_exception());
CLEAR_PENDING_EXCEPTION;
THROW_HANDLE_0(ex);
}
So if that's the case and jni_FindClass() was reallycalled from
ClassLoader$NativeLibrary, then jni_FindClass() calles
ClassLoader$NativeLibrary().getFromClass() to find out the
corresponding
context class which is supposed to be saved there in a field of type
java.util.Stack named "nativeLibraryContext":
// Invoked in the VM to determine the context class in
// JNI_Load/JNI_Unload
static Class<?> getFromClass() {
return ClassLoader.nativeLibraryContext.peek().fromClass;
}
Unfortunately, "nativeLibraryContext" doesn't contain any entry
at this
point and the invocation of Stack.peek() will throw the exception
shown
before. In general, the "nativeLibraryContext" stack will be
filled later
on
in Runtime.loadLibrary0() like this:
NativeLibrary lib = new NativeLibrary(fromClass, name, isBuiltin);
nativeLibraryContext.push(lib);
try {
lib.load(name, isBuiltin);
} finally {
nativeLibraryContext.pop();
}
such that it always contains at least one element later when
jni_FindClass()
will be invoked.
So in summary, the problem is that the implementors of 8005716
didn't
took
into account that calling
ClassLoader$NativeLibrary.findBuiltinLib() may
trigger a call to jni_FindClass() if we are running on a system
with an
unsupported character encoding.
I'd suggest the following fix for this problem:
Change ClassLoader$NativeLibrary().getFromClass() to return null
if the
stack is empty instead of throwing an exception:
static Class<?> getFromClass() {
return ClassLoader.nativeLibraryContext.empty() ?
null : ClassLoader.nativeLibraryContext.peek().fromClass;
}
Unfortunately this also requires a HotSpot change in
jni_FindClass() in
order to properly handle the new 'null' return value:
if (k.not_null()) {
loader = Handle(THREAD, k->class_loader());
// Special handling to make sure JNI_OnLoad and
JNI_OnUnload are
executed
// in the correct class context.
if (loader.is_null() &&
k->name() ==
vmSymbols::java_lang_ClassLoader_NativeLibrary()) {
JavaValue result(T_OBJECT);
JavaCalls::call_static(&result, k,
vmSymbols::getFromClass_name(),
vmSymbols::void_class_signature(),
thread);
if (HAS_PENDING_EXCEPTION) {
Handle ex(thread, thread->pending_exception());
CLEAR_PENDING_EXCEPTION;
THROW_HANDLE_0(ex);
}
oop mirror = (oop) result.get_jobject();
if (oopDesc::is_null(mirror)) {
loader = Handle(THREAD,
SystemDictionary::java_system_loader());
} else {
loader = Handle(THREAD,
InstanceKlass::cast(java_lang_Class::as_Klass(mirror))->class_loader());
protection_domain = Handle(THREAD,
InstanceKlass::cast(java_lang_Class::as_Klass(mirror))->protection_domain());
}
}
} else {
// We call ClassLoader.getSystemClassLoader to obtain the
system
class
loader.
loader = Handle(THREAD,
SystemDictionary::java_system_loader());
}
These changes are sufficient to solve the problem in Java 8.
Unfortunately,
that's still not enough in Java 9 because there the loading of the
extended
charsets has been delegated to ServiceLoader. But ServiceLoader
calls
ClassLoader.getBootstrapResources() which calls
sun.misc.Launcher.getBootstrapClassPath(). This leads to another
problem
during class initialization of sun.misc.Launcher if running on an
unsupported locale.
The first thing done in sun.misc.Launcher.<clinit> is the
initialisation
of
the bootstrap URLClassPath in the Launcher. However this
initialisation
will
eventually call Charset.isSupported() and if we are running on an
unsupported locale this will inevitably end in another recursive
call to
ServiceLoader. But as explained below, ServiceLoader will query the
Launcher's bootstrap URLClassPath which will be still
uninitialized at
that
point.
So we'll have to additionally guard guard against this situation
on JDK 9
like this:
private static Charset lookupExtendedCharset(String charsetName) {
if (!sun.misc.VM.isBooted() || // see
lookupViaProviders()
sun.misc.Launcher.getBootstrapClassPath() == null)
return null;
This fixes the crashes, but still at the price of not having the
extended
charsets available during initialization until
Launcher.getBootstrapClassPath is set up properly. This may be
still a
problem if the jdk is installed in a directory which contains
characters
specific to an extended encoding or if we have such characters in
the
command line arguments.
Thank you and best regards,
Volker
Mixed stack trace of the initial EmptyStackException for unsupported
charsets described before:
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code,
C=native
code)
j java.util.Stack.peek()Ljava/lang/Object;+1
j
java.lang.ClassLoader$NativeLibrary.getFromClass()Ljava/lang/Class;+3
v ~StubRoutines::call_stub
V [libjvm.so+0x9d279a] JavaCalls::call_helper(JavaValue*,
methodHandle*,
JavaCallArguments*, Thread*)+0x6b4
V [libjvm.so+0xcad591] os::os_exception_wrapper(void
(*)(JavaValue*,
methodHandle*, JavaCallArguments*, Thread*), JavaValue*,
methodHandle*,
JavaCallArguments*, Thread*)+0x45
V [libjvm.so+0x9d20cf] JavaCalls::call(JavaValue*, methodHandle,
JavaCallArguments*, Thread*)+0x8b
V [libjvm.so+0x9d1d3b] JavaCalls::call_static(JavaValue*,
KlassHandle,
Symbol*, Symbol*, JavaCallArguments*, Thread*)+0x139
V [libjvm.so+0x9d1e3f] JavaCalls::call_static(JavaValue*,
KlassHandle,
Symbol*, Symbol*, Thread*)+0x9d
V [libjvm.so+0x9e6588] jni_FindClass+0x428
C [libjava.so+0x20208] JNU_CallStaticMethodByName+0xff
C [libjava.so+0x21cae] jnuEncodingSupported+0x61
C [libjava.so+0x22125] JNU_GetStringPlatformChars+0x125
C [libjava.so+0xedcd]
Java_java_lang_ClassLoader_00024NativeLibrary_findBuiltinLib+0x8b
j
java.lang.ClassLoader$NativeLibrary.findBuiltinLib(Ljava/lang/String;)Ljava/lang/String;+0
j
java.lang.ClassLoader.loadLibrary0(Ljava/lang/Class;Ljava/io/File;)Z+4
j
java.lang.ClassLoader.loadLibrary(Ljava/lang/Class;Ljava/lang/String;Z)V+228
j
java.lang.Runtime.loadLibrary0(Ljava/lang/Class;Ljava/lang/String;)V+54
j java.lang.System.loadLibrary(Ljava/lang/String;)V+7
j java.lang.System.initializeSystemClass()V+113
v ~StubRoutines::call_stub
V [libjvm.so+0x9d279a] JavaCalls::call_helper(JavaValue*,
methodHandle*,
JavaCallArguments*, Thread*)+0x6b4
V [libjvm.so+0xcad591] os::os_exception_wrapper(void
(*)(JavaValue*,
methodHandle*, JavaCallArguments*, Thread*), JavaValue*,
methodHandle*,
JavaCallArguments*, Thread*)+0x45
V [libjvm.so+0x9d20cf] JavaCalls::call(JavaValue*, methodHandle,
JavaCallArguments*, Thread*)+0x8b
V [libjvm.so+0x9d1d3b] JavaCalls::call_static(JavaValue*,
KlassHandle,
Symbol*, Symbol*, JavaCallArguments*, Thread*)+0x139
V [libjvm.so+0x9d1e3f] JavaCalls::call_static(JavaValue*,
KlassHandle,
Symbol*, Symbol*, Thread*)+0x9d
V [libjvm.so+0xe3cceb] call_initializeSystemClass(Thread*)+0xb0
V [libjvm.so+0xe44444]
Threads::initialize_java_lang_classes(JavaThread*,
Thread*)+0x21a
V [libjvm.so+0xe44b12] Threads::create_vm(JavaVMInitArgs*,
bool*)+0x4a6
V [libjvm.so+0xa19bd7] JNI_CreateJavaVM+0xc7
C [libjli.so+0xa520] InitializeJVM+0x154
C [libjli.so+0x8024] JavaMain+0xcc