JDK-8257790 : ExitOnOutOfMemoryError doesn't cover OOMEs from Java libraries
  • Type: Enhancement
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 16
  • Priority: P4
  • Status: Closed
  • Resolution: Won't Fix
  • OS: generic
  • CPU: generic
  • Submitted: 2020-12-04
  • Updated: 2024-12-18
  • Resolved: 2020-12-10
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Description
Not only ExitOnOutOfMemoryError, other flags such as OnOutOfMemoryError, CrashOnOutOfMemoryError and HeapDumpOnOutOfMemoryError all lie with the internal function debug.cpp::report_java_out_of_memory of JVM. 

report_java_out_of_memory() only covers OOMEs that raise from the internal JVM(hotspot). Some OOMEs are thrown in Java libraries. In short words, OOMEs are not reported to hotspot in those cases. here is non-exhaustive lists. OOME("Direct buffer memory") is one of them.

$ grep "new OutOfMemoryError" -R java.base/
java.base/share/classes/jdk/internal/util/ArraysSupport.java:            throw new OutOfMemoryError("Required array length too large");
java.base/share/classes/jdk/internal/misc/Unsafe.java:            throw new OutOfMemoryError("Unable to allocate " + bytes + " bytes");
java.base/share/classes/jdk/internal/misc/Unsafe.java:            throw new OutOfMemoryError("Unable to allocate " + bytes + " bytes");
java.base/share/classes/jdk/internal/icu/text/BidiBase.java:            throw new OutOfMemoryError("Failed to allocate memory for "
java.base/share/classes/jdk/internal/icu/text/BidiBase.java:            throw new OutOfMemoryError("Failed to allocate memory for "
java.base/share/classes/jdk/internal/icu/text/BidiBase.java:            throw new OutOfMemoryError("Failed to allocate memory for paras");
java.base/share/classes/jdk/internal/icu/text/BidiBase.java:                throw new OutOfMemoryError("Failed to allocate memory for openings");
java.base/share/classes/java/util/Base64.java:                    throw new OutOfMemoryError("Encoded size is too large");
java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java:                throw new OutOfMemoryError(OOME_MSG);
java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java:                        throw new OutOfMemoryError(OOME_MSG);
java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java:                throw new OutOfMemoryError(OOME_MSG);
java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java:                        throw new OutOfMemoryError(OOME_MSG);
java.base/share/classes/java/util/jar/JarFile.java:                throw new OutOfMemoryError("Required array size too large");
java.base/share/classes/java/util/StringJoiner.java:            throw new OutOfMemoryError("Requested array size exceeds VM limit");
java.base/share/classes/java/util/regex/Pattern.java:            throw new OutOfMemoryError("Required pattern length too large");
java.base/share/classes/java/nio/Bits.java:            throw new OutOfMemoryError
java.base/share/classes/java/nio/file/Files.java:                throw new OutOfMemoryError("Required array size too large");
java.base/share/classes/java/io/InputStream.java:                    throw new OutOfMemoryError("Required array size too large");
java.base/share/classes/java/lang/String.java:                throw new OutOfMemoryError("Required length exceeds implementation limit");
java.base/share/classes/java/lang/String.java:            throw new OutOfMemoryError("Required length exceeds implementation limit");
java.base/share/classes/java/lang/AbstractStringBuilder.java:            throw new OutOfMemoryError("Required length exceeds implementation limit");
java.base/share/classes/java/lang/StringUTF16.java:            throw new OutOfMemoryError("UTF16 String size is " + len +
java.base/share/classes/java/lang/StringUTF16.java:           throw new OutOfMemoryError("Required length exceeds implementation limit");
java.base/share/classes/java/lang/StringLatin1.java:            throw new OutOfMemoryError("Required length exceeds implementation limit");
java.base/share/classes/java/lang/StringConcatHelper.java:        throw new OutOfMemoryError("Overflow: String length out of range");

Back to ExitOnOutOfMemoryError, which is introduced by JDK-8138745, it intercepts OOME and abruptly exits JVM with exitcode 3. If users don't catch and suppress it, this error will keep unwinding the stack and eventually exits JVM. I think it's a good manner and that's why ExitOnOutOfMemoryError is false by default.

Hotspot itself doesn't have any effective way to relief OOMEs, so the termination is doomed, but that may not be true in Java libraries. The higher level of abstraction, more wiggle room  there is. Exiting JVM at first OOME seems to be arbitrary. I intend to believe this behavior is by design. On that other side, it's misleading indeed to have a product flag ExitOnOutOfMemoryError -- "JVM exits on the first occurrence of an out-of-memory error".

Comments
almost same.
27-08-2021

got it. thanks. I will close it as "WON'T FIX". On the other side, we can make description of those flags clearer for the end-users.
10-12-2020

> As you explained, the OOME here refers to the "true" OOME from JVM, but java users usually won't step into JVM. Such users should not be using -XX flags then. These are JVM flags and pertain to things that happen in the JVM. If this is not clear somewhere we can make it clearer. My take is that this is not an issue, other than perhaps to clarify any applicable documentation. I would strongly oppose any attempt to hook in to the java.lang.OutOfMemoryError constructor to call into the VM to have it apply these flags to that case.
10-12-2020

hi, [~dholmes], Thank you for explaining it. I understand that java libraries and user code may have wiggle room to relief OOM Errors. eg. they may manually trigger System.gc() or unmap a big chunk of memory to free up some native memories and retry. I just feel that the current option ExitOnOutOfMemoryError -- "JVM exits on the first occurrence of an out-of-memory error" is a little misleading. Please note that those are all product flags and people do read and rely on them. As you explained, the OOME here refers to the "true" OOME from JVM, but java users usually won't step into JVM. Technically speaking, it's possible to intercept the construction of any OOME and notify JVM. I hesitate because it will alter java program behavior. so may I know what's your take on this issue? Shall we leave it or try to intercept all OOMEs ?
08-12-2020

I changed "Java runtime" to "Java libraries" as the Java runtime is generally taken to mean the VM runtime. The VM flags ExitOnOutOfMemory, OnOutOfMemoryError, CrashOnOutOfMemoryError and HeapDumpOnOutOfMemoryError, all deal with a true out-of-memory condition detected by the object allocation routines in the VM. They are not intended to interact with Java library, or application, code that explicitly does "throw new OutOfMemoryError".
06-12-2020

please find out the attachment test. it's a bit weird that user code suppress an OOME, but some java users really reply on ExitOnOutOfMemoryError. Just like hangInOtherThread, some service may end up with many defunct threads.
05-12-2020