JDK-6362318 : (thread) uncaught exception handler can be silenced by OutOfMemoryError
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 5.0,7,9
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2005-12-13
  • Updated: 2024-04-12
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Duplicate :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
The attached program MemoryEater1 triggers multiple OutOfMemoryErrors and
eventually exits due to OOME.  However, no message about the exception is
printed.  The only indication an exception occurred is the non-zero exit
status.

$ $JAVA_HOME/bin/java -client -showversion -Xmx128m MemoryEater1
java version "1.6.0-ea"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.6.0-ea-b58)
Java HotSpot(TM) Client VM (build 1.6.0-ea-b58, mixed mode)
$ echo $?
1

jdk1.5.0 behaves similarly.

The 1.4.2 jdk at least printed a message indicating an exception
occurred, although the message was incomplete (the type of the
exception and newline were missing):

$ $JAVA_HOME/bin/java -client -showversion -Xmx128m MemoryEater1
java version "1.4.2_07"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_07-b05)
Java HotSpot(TM) Client VM (build 1.4.2_07-b05, mixed mode)

Exception in thread "main"$
$ echo $?
1

Comments
SUGGESTED FIX Here's a simpler attempted fix that simply avoids allocation and tries to provide basic information whenever OOME is encountered: // static constants for uncaughtException to avoid allocation static final String EXCEPTION_IN_THREAD = "Exception in thread \""; static final String OOME_THREAD_NAME = "\n UncaughtException: secondary OutOfMemoryError thrown trying to print Thread name"; static final String OOME_STACK_TRACE = " UncaughtException: secondary OutOfMemoryError trying to print stacktrace "; static final String OOME_SECONDARY = " UncaughtException: secondary OutOfMemoryError trying to print original exception type"; public void uncaughtException(Thread t, Throwable e) { if (parent != null) { parent.uncaughtException(t, e); } else { Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler(); if (ueh != null) { // we could do some safety-guards around this too ueh.uncaughtException(t, e); } else if (!(e instanceof ThreadDeath)) { // we want to print a stacktrace but we need to be wary // of hitting a secondary OutOfMemoryError. This is more // likely if processing OOME but we do the same for all // exception types. Avoid direct allocations and implicit // string concatenation. The VM will print a basic info // message if we throw a secondary exception. try { System.err.print(EXCEPTION_IN_THREAD); System.err.print(t.getName()); System.err.println('"'); // trailing quote } catch (OutOfMemoryError oome) { System.err.println(OOME_THREAD_NAME); // try to at least report the original exception info try { System.err.println(e); } catch (OutOfMemoryError oome2) { System.out.println(OOME_SECONDARY); } return; } try { e.printStackTrace(System.err); } catch (OutOfMemoryError oome) { System.out.println(OOME_STACK_TRACE); // try to at least report the original exception info try { System.err.println(e); } catch (OutOfMemoryError oome2) { System.out.println(OOME_SECONDARY); } } } } }
02-03-2011

EVALUATION Just to add to previous comments, the HotSpot changes are in hs21-b02 and so are in jdk7-b130. This means that the test now prints a message so that it's reasonably clear that the heap is exhausted: Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
01-03-2011

EVALUATION The VM has addressed part of the concern here, under 6472925, by ensuring that it reports if the uncaughtExceptionHandler completes with an exception pending. At least that way the user can see that OOME occurred and there is no silent termination of the VM.
01-03-2011

EVALUATION If heap is fully exhausted and ThreadGroup.uncaughtException handles the OutOfMemoryError, then there isn't enough memory to do anyting at all in the way of reporting: not even a single println with a static, preallocated String, and not even System.exit with a "special" code. In the worst case during recent tests only Runtime.getRuntime().halt(n) was possible to complete as a means of communicating. (A native method call that bypasses the Java streams and outputs directly might also work on some platforms, but might also create further complications.) So, based on the bug notes, it appears that recent versions of SE are obviously doing more object instantiations than older versions did. The (incomplete) suggested fix adds a static byte array instantiated by the "system" ThreadGroup instance when it is instantiated. Method uncaughtException sets this array field to null, enabling a GC and success by the handler, iff another thread doesn't steal the resulting heap space first. Making the "emergency space" byte array a bit bigger than needed provides some protection against another thread's allocations and also mostly obviates the need to complicate the method with preallocated static message strings. But using preallocated strings (as described in CR 6534149), would add some theoretical robustness for the odd corner case. However, in that circumstance the program behavior is likely to be variable, so part of the time the uncaught handler messages would get out. These changes are incomplete and only tested on one platform. At a minimum a 'reliable' value for the emergency byte array would need to be selected to work on all platforms. Further steps to improve this fix (in priority order) include: 0) Per-platform array sizes if there is a significant spread. 1) Executing the method within a block synchronized on the ThreadGroup class object to hold off a second thread entering from an OOM. 2) Attempting to reallocate the emergency space before returning, "walking" the size down as needed until OOM is not thrown (or giving up at a too small size). 3) Using preinitialized, static strings and avoiding the string concatenation would add some theoretical robustness. This is already done for the case where a second OOM is thrown within the method, but I'm pessimistic that this println will ever succeed. 4) Dig into the methods uncaughtException calls to see if the extra instantiations there could be avoided. Actually, a "quick look" should be a higher priority, in case there's something incredibly lucky involved. The "MemoryEater1" test case should be made into a unit test. Licensees should take note of this fix, assuming it pans out. User defined uncaught exception handlers could also use this strategy to avoid being silenced by OOM. Finally, back porting this to SE 5 and 1.4.2 might be useful.
14-05-2007

SUGGESTED FIX Here is the start of a fix: http://javaweb.sfbay/java/jdk/ws/libs/rev/6362318/ Here's a "diff" that might be visible on the Internet: ++ ThreadGroup.java Mon May 14 13:14:10 2007 @@ -38,11 +38,11 @@ * A thread is allowed to access information about its own thread * group, but not to access information about its thread group's * parent thread group or any other thread groups. * * @author unascribed - * @version %I%, %G% + * @version 1.69, 03/13/07 * @since JDK1.0 */ /* The locking strategy for this code is to try to lock only one level of the * tree wherever possible, but otherwise to lock from the bottom up. * That is, from child thread groups to parents. @@ -69,16 +69,36 @@ int ngroups; ThreadGroup groups[]; /** + * When heap is absolutely exausted there isn't enough memory for + * uncaughtException to get something meaningful out, or even invoke + * exit with a special value. This space is preallocated by the system + * group ctor in case it's needed for an uncaught exception. + */ + + static private byte[] emergencyspace; + + /** * Creates an empty Thread group that is not in any Thread group. * This method is used to create the system Thread group. */ private ThreadGroup() { // called from C code this.name = "system"; this.maxPriority = Thread.MAX_PRIORITY; + + /** + * Allocate the emergency space. + * XXXX: This size 500 is double what was needed on + * a Sparc Solaris test system. This is prototype code. Production + * should either use a (parameterized) value proven to work on all + * platforms or use a per-platform method to define the array size if + * optimization is important. + */ + + emergencyspace = new byte[500]; } /** * Constructs a new thread group. The parent of this new group is * the thread group of the currently running thread. @@ -976,11 +996,21 @@ * * @param t the thread that is about to exit. * @param e the uncaught exception. * @since JDK1.0 */ + + private static String oomMessage = + "OutOfMemory thrown in ThreadGroup.uncaughtException (too little emergency space)"; + public void uncaughtException(Thread t, Throwable e) { + + // Free up enough heap to be allow output to succeed in case we got + // here via an OOM. + emergencyspace = null; + + try { if (parent != null) { parent.uncaughtException(t, e); } else { Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler(); @@ -987,14 +1017,20 @@ if (ueh != null) { ueh.uncaughtException(t, e); } else if (!(e instanceof ThreadDeath)) { System.err.print("Exception in thread \"" + t.getName() + "\" "); + // An OOM has no stack trace, so the overall heap needed + // in this case is in fact bounded. e.printStackTrace(System.err); } } + } catch (OutOfMemoryError oom) { + // Not enough emergency space! + System.err.println(oomMessage); } + }
14-05-2007

EVALUATION It should be possible to get "something" out to provide information for the outermost exception in this situation when lack of memory makes regular handling impossible.
16-01-2006