RFR(M): 8081674: EmptyStackException at startup if running with extended or unsupported charset (original) (raw)

Mandy Chung mandy.chung at oracle.com
Mon Jun 8 23:35:06 UTC 2015


Hi Volker,

Your patch reminds me the question I have about loading zip library.

In JDK 9 (and earlier release), zip library is loaded by the VM during startup (see ClassLoader::load_zip_library). I think loadLibrary("zip") is no longer needed to be called from System::initializeSystemClass method and instead it can be loaded by java.util.zip.ZipFile static initializer.

Do you mind to try the patch (below)? That may be a simpler fix.

I never like the way jni_FindClass to look for the class context by calling the NativeLibrary::getFromClass method. I will have to look deeper other alternative to clean that up. If taking out loadLibrary("zip") resolves your issue, this will give us time to come up with a better clean-up in the future.

Mandy [1] https://bugs.openjdk.java.net/browse/JDK-4429040

diff --git a/src/share/classes/java/lang/System.java b/src/share/classes/java/lang/System.java --- a/src/share/classes/java/lang/System.java +++ b/src/share/classes/java/lang/System.java @@ -1192,10 +1192,6 @@ setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding"))); setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding")));

available). Terminator.setup();

diff --git a/src/share/classes/java/util/zip/ZipFile.java b/src/share/classes/java/util/zip/ZipFile.java --- a/src/share/classes/java/util/zip/ZipFile.java +++ b/src/share/classes/java/util/zip/ZipFile.java @@ -83,6 +83,7 @@

  static {
      /* Zip library is loaded from System.initializeSystemClass */

On 06/08/2015 07:23 AM, Volker Simonis wrote:

Hi,

can I please get a review at least for the part of this fix which is common for jdk8 and jdk9. It's only a few lines in src/share/vm/prims/jni.cpp for the hotspot repository and one line src/java.base/share/classes/java/lang/ClassLoader.java for the jdk repository. Thanks, Volker

On Tue, Jun 2, 2015 at 6:12 PM, Volker Simonis <volker.simonis at gmail.com> wrote: 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. viVN.TCVN on Linux) results in an immediate VM failure with jdk 8 and 9: export LANG=viVN.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). JavajavalangClassLoader00024NativeLibraryfindBuiltinLib(), 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 = JNIFALSE; static jboolean jnuEncodingSupported(JNIEnv *env) { jboolean exe; if (isJNUEncodingSupported == JNITRUE) { return JNITRUE; } isJNUEncodingSupported = (jboolean) JNUCallStaticMethodByName ( 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 JNUCallStaticMethodByName() in file jniutil.c), we have to call jniFindClass() to convert the symbolic class name "java.nio.charset.Charset" into a class reference. But unfortunately, jniFindClass() (from jni.cpp in libjvm.so) has a special handling if called from java.lang.ClassLoader$NativeLibrary to ensure that JNIOnLoad/JNIOnUnload are executed in the correct class context: instanceKlassHandle k (THREAD, thread->securitygetcallerclass(0)); if (k.notnull()) { loader = Handle(THREAD, k->classloader()); // Special handling to make sure JNIOnLoad and JNIOnUnload are executed // in the correct class context. if (loader.isnull() && k->name() == vmSymbols::javalangClassLoaderNativeLibrary()) { JavaValue result(TOBJECT); JavaCalls::callstatic(&result, k, vmSymbols::getFromClassname(), vmSymbols::voidclasssignature(), thread); if (HASPENDINGEXCEPTION) { Handle ex(thread, thread->pendingexception()); CLEARPENDINGEXCEPTION; THROWHANDLE0(ex); } So if that's the case and jniFindClass() was reallycalled from ClassLoader$NativeLibrary, then jniFindClass() 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 // JNILoad/JNIUnload 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 jniFindClass() 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 jniFindClass() 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 jniFindClass() in order to properly handle the new 'null' return value: if (k.notnull()) { loader = Handle(THREAD, k->classloader()); // Special handling to make sure JNIOnLoad and JNIOnUnload are executed // in the correct class context. if (loader.isnull() && k->name() == vmSymbols::javalangClassLoaderNativeLibrary()) { JavaValue result(TOBJECT); JavaCalls::callstatic(&result, k, vmSymbols::getFromClassname(), vmSymbols::voidclasssignature(), thread); if (HASPENDINGEXCEPTION) { Handle ex(thread, thread->pendingexception()); CLEARPENDINGEXCEPTION; THROWHANDLE0(ex); } oop mirror = (oop) result.getjobject(); if (oopDesc::isnull(mirror)) { loader = Handle(THREAD, SystemDictionary::javasystemloader()); } else { loader = Handle(THREAD, InstanceKlass::cast(javalangClass::asKlass(mirror))->classloader()); protectiondomain = Handle(THREAD, InstanceKlass::cast(javalangClass::asKlass(mirror))->protectiondomain()); } } } else { // We call ClassLoader.getSystemClassLoader to obtain the system class loader. loader = Handle(THREAD, SystemDictionary::javasystemloader()); } 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. 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::callstub V [libjvm.so+0x9d279a] JavaCalls::callhelper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*)+0x6b4 V [libjvm.so+0xcad591] os::osexceptionwrapper(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::callstatic(JavaValue*, KlassHandle, Symbol*, Symbol*, JavaCallArguments*, Thread*)+0x139 V [libjvm.so+0x9d1e3f] JavaCalls::callstatic(JavaValue*, KlassHandle, Symbol*, Symbol*, Thread*)+0x9d V [libjvm.so+0x9e6588] jniFindClass+0x428 C [libjava.so+0x20208] JNUCallStaticMethodByName+0xff C [libjava.so+0x21cae] jnuEncodingSupported+0x61 C [libjava.so+0x22125] JNUGetStringPlatformChars+0x125 C [libjava.so+0xedcd] JavajavalangClassLoader00024NativeLibraryfindBuiltinLib+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::callstub V [libjvm.so+0x9d279a] JavaCalls::callhelper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*)+0x6b4 V [libjvm.so+0xcad591] os::osexceptionwrapper(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::callstatic(JavaValue*, KlassHandle, Symbol*, Symbol*, JavaCallArguments*, Thread*)+0x139 V [libjvm.so+0x9d1e3f] JavaCalls::callstatic(JavaValue*, KlassHandle, Symbol*, Symbol*, Thread*)+0x9d V [libjvm.so+0xe3cceb] callinitializeSystemClass(Thread*)+0xb0 V [libjvm.so+0xe44444] Threads::initializejavalangclasses(JavaThread*, Thread*)+0x21a V [libjvm.so+0xe44b12] Threads::createvm(JavaVMInitArgs*, bool*)+0x4a6 V [libjvm.so+0xa19bd7] JNICreateJavaVM+0xc7 C [libjli.so+0xa520] InitializeJVM+0x154 C [libjli.so+0x8024] JavaMain+0xcc



More information about the hotspot-dev mailing list