We originally encountered this in the real-time VM where ITC gave more opportunity for injecting errors into the initialization process - see 6671509.
The basic symptom is if an exception is thrown from a specific point during the initialization of the VM, then upon calling destroyJavaVM an unexpected pending exception is found when calling JavaThread::invoke_shutdown_hooks() and this triggers a fatal error handler in the ExceptionMark. It was also observed that the thread encountering the fatal error was "main" when it was expected to be the DestroyJavaVM thread.
The launcher, having created the VM and attached the current thread, uses this basic pattern for error handling:
335 if ((*env)->ExceptionOccurred(env)) {
336 JLI_ReportExceptionDescription(env);
337 JLI_ReportErrorMessage(JNI_ERROR);
338 goto leave;
339 }
where ReportExceptionDescription clears the pending exception and "leave" has the following code:
514 leave:
515 /*
516 * Wait for all non-daemon threads to end, then destroy the VM.
517 * This will actually create a trivial new Java waiter thread
518 * named "DestroyJavaVM", but this will be seen as a different
519 * thread from the one that executed main, even though they are
520 * the same C thread. This allows mainThread.join() and
521 * mainThread.isAlive() to work as expected.
522 */
523 (*vm)->DestroyJavaVM(vm);
524
525 return ret;
However there is one piece of code that doesn't use this pattern:
422 mainClassName = NewPlatformString(env, classname);
423 if (mainClassName == NULL) {
424 JLI_ReportErrorMessage(CLS_ERROR2, classname, GEN_ERROR);
425 goto leave;
426 }
as a result an exception during NewPlatformString remains pending. Then due to a second flaw in the error sequence this pending exception leads to the crash on exit. The second bug is that in executing "goto leave" the code to detach the current thread from the VM is skipped. This has a number of consequences:
1. As the thread doesn't detach, the pending exception is not processed as a normal uncaught exception would be. Hence it remains pending.
2. attach_current_thread (inside DestroyJavaVM) becomes a no-op because it sees it is already attached and so we skip the code that would have caught the fact there was a pending exception ie the ExceptionMark in attach_current_thread
3. It explains why pstack shows the thread as "main" rather than "DestroyJavaVM" because there was no actual attach of the "new" Java thread.
So not only does the launcher have a bug where it leaves the exception pending in one case, but it has a bug because it skips detaching of the current thread before destroying the VM. If it always detached then the first bug wouldn't be a bug - and in fact some/all of those ReportExceptionDescription calls would be unnecessary because any pending exception would be handled by the "uncaught exception" mechanism.
With the fix to 6742159 this particular exception path can't occur any more. But the logic could still be simplified if the main thread was always detached.
The basic symptom is if an exception is thrown from a specific point during the initialization of the VM, then upon calling destroyJavaVM an unexpected pending exception is found when calling JavaThread::invoke_shutdown_hooks() and this triggers a fatal error handler in the ExceptionMark. It was also observed that the thread encountering the fatal error was "main" when it was expected to be the DestroyJavaVM thread.
The launcher, having created the VM and attached the current thread, uses this basic pattern for error handling:
335 if ((*env)->ExceptionOccurred(env)) {
336 JLI_ReportExceptionDescription(env);
337 JLI_ReportErrorMessage(JNI_ERROR);
338 goto leave;
339 }
where ReportExceptionDescription clears the pending exception and "leave" has the following code:
514 leave:
515 /*
516 * Wait for all non-daemon threads to end, then destroy the VM.
517 * This will actually create a trivial new Java waiter thread
518 * named "DestroyJavaVM", but this will be seen as a different
519 * thread from the one that executed main, even though they are
520 * the same C thread. This allows mainThread.join() and
521 * mainThread.isAlive() to work as expected.
522 */
523 (*vm)->DestroyJavaVM(vm);
524
525 return ret;
However there is one piece of code that doesn't use this pattern:
422 mainClassName = NewPlatformString(env, classname);
423 if (mainClassName == NULL) {
424 JLI_ReportErrorMessage(CLS_ERROR2, classname, GEN_ERROR);
425 goto leave;
426 }
as a result an exception during NewPlatformString remains pending. Then due to a second flaw in the error sequence this pending exception leads to the crash on exit. The second bug is that in executing "goto leave" the code to detach the current thread from the VM is skipped. This has a number of consequences:
1. As the thread doesn't detach, the pending exception is not processed as a normal uncaught exception would be. Hence it remains pending.
2. attach_current_thread (inside DestroyJavaVM) becomes a no-op because it sees it is already attached and so we skip the code that would have caught the fact there was a pending exception ie the ExceptionMark in attach_current_thread
3. It explains why pstack shows the thread as "main" rather than "DestroyJavaVM" because there was no actual attach of the "new" Java thread.
So not only does the launcher have a bug where it leaves the exception pending in one case, but it has a bug because it skips detaching of the current thread before destroying the VM. If it always detached then the first bug wouldn't be a bug - and in fact some/all of those ReportExceptionDescription calls would be unnecessary because any pending exception would be handled by the "uncaught exception" mechanism.
With the fix to 6742159 this particular exception path can't occur any more. But the logic could still be simplified if the main thread was always detached.
- duplicates
-
JDK-6742159 (launcher) improve the java launching mechanism
-
- Closed
-