JDK-8290196 : 12.8: Clarify the definition of program exit
  • Type: Enhancement
  • Component: specification
  • Sub-Component: language
  • Affected Version: 19
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2022-07-12
  • Updated: 2022-12-13
  • Resolved: 2022-12-13
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.
JDK 20
20Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Description
Program exit is best described in terms of the threads in a Java program, but there is an extralinguistic nature to program exit that makes it tricky to specify in the JLS. Notably, the Java API makes a distinction between daemon threads and non-daemon threads that is material to program exit but is not mentioned elsewhere in the JLS, resulting in 12.8 referring to "... threads that are *not daemon threads* ..." without explanation or context.

In addition, 12.8 refers to the `Runtime.exit` and `System.exit` methods whose behavior since Java 1.3 has been deeply intertwined with a third kind of thread, shutdown hooks. 12.8 was last updated in the JLS Second Edition, prior to Java 1.3, so it omits any reference to shutdown hooks despite them being ordinary Java code and thus part of the program. It would be appropriate for 12.8 to recognize the role that shutdown hooks play at program exit. (From the perspective of the program's other threads -- in contrast to the API specification of shutdown hooks which is focused on the behavior of the Java Virtual Machine as it shuts down. Focusing on the program, not the JVM, has been the goal ever since 12.8 "Program Exit" was itself recast from 12.9 "Virtual Machine Exit" in the JLS First Edition.)

Finally, 12.8 says that threads "terminate". This makes no distinction between (i) a thread that exits naturally, due to having completed execution of its `run` method, and (ii) a thread that is forcefully destroyed when the JVM halts at the end of its shutdown procedure. In (i), `finally` clauses and uncaught exception handlers get to execute; in (ii), they don't. 12.8 has always been pleasingly short but this brevity is a liability when it comes to giving a thorough account of what Java programmers can expect.
Comments
***This comment gives an initial draft of the revised text. It is not up to date. The current proposal for revised text is given in the comment at 2022-07-15 10:48.*** Revised section below. * 12.8 Program Exit A program consists of one or more threads of execution. A thread is either a non-daemon thread, a daemon thread, or a shutdown hook. Readers are referred to the API specifications of `Thread` and `Runtime` for details of how threads obtain daemon status, and how shutdown hooks are registered. A thread _exits_ if either (i) its `run` method completes normally, or (ii) its `run` method completes abruptly and the relevant uncaught exception handler (11.3) completes normally or abruptly. With no code left to run, the thread has completed execution and therefore has no current method (JVMS 2.5.1). The run-time data areas of the thread are destroyed. A program _exits_ when one of the following has occurred: 1. All of its non-daemon threads have exited, and all of the shutdown hooks which consequently were started by the Java Virtual Machine, if any, have exited. 2. A thread invoked System.exit or Runtime.exit, and all of the shutdown hooks which consequently were started by the Java Virtual Machine, if any, have exited. 3. A thread invokes Runtime::halt. (No shutdown hooks are started in this situation.) 4. A thread invoked JNI_DestroyJavaVM from native code, and all other non-daemon threads have exited, and all of the shutdown hooks which consequently were started by the Java Virtual Machine, if any, have exited. 5. The Java Virtual Machine implementation recognized an external event as requesting termination of the Java Virtual Machine, and all of the shutdown hooks which consequently were started by the Java Virtual Machine, if any, have exited. The nature of the event is outside the scope of this specification, but is necessarily something that a Java Virtual Machine implementation can handle reliably. An example is receiving a signal from the operating system. 6. An external event occurs that the Java Virtual Machine implementation cannot handle. (No shutdown hooks are started in this situation.) The nature of the event is outside the scope of this specification, but is necessarily something that a Java Virtual Machine implementation cannot recognize, handle, or recover from in any way. Examples include a fatal error occurring in the process running the implementation, or power being removed from the computer running the implementation. Upon program exit, the Java Virtual Machine _stops_ any daemon and non-daemon threads that have not yet exited, and then halts. Stopping a thread causes execution of the thread's current method to cease. The current method does not complete normally or abruptly. No `finally` clause of any method in the thread is executed, nor any uncaught exception handler. The run-time data areas of the thread are destroyed. Note that program exit does not occur until after shutdown hooks, if any, have exited. The reason for this "late" exit is twofold. First, shutdown hooks are part of the program, so the program has not yet exited while they are running. Second, in the common case of Runtime.exit being invoked, non-daemon threads that were running before the invocation continue to run after the invocation, concurrently with shutdown hooks; the ongoing existence of non-daemon threads is proof that the program has not yet exited. Only when the last thread is _stopped_ has the program truly exited. Note that a thread _exits_ of its own accord, when it has no more code to run, while a thread that is _stopped_ by the Java Virtual Machine still has code to run but is unable to run it. A thread being stopped is one of three scenarios where a method in a Java program does not complete. The other scenarios are if the method blocks indefinitely (waiting on something that can never occur) or if the method loops infinitely.
18-08-2022

> The JLS says that the program exits when the last non-daemon thread terminates -- you can't tell what happened to the JVM. Hmmm so what is a "program"? And how does such a program start? And what is the relationship between that program and the JVM? Does the JNI invocation API deal with programs, or a JVM or both? The JLS defers to the JVMS Chapter 5 for the JVM starting but that itself is actually quite misleading in what it says: "The Java Virtual Machine starts up by creating an initial class or interface using the bootstrap class loader (§5.3.1) or a user-defined class loader (§5.3.2). The Java Virtual Machine then links the initial class or interface, initializes it, and invokes the public static method void main(String[])." The reality is the JVM starts up because JNI CreateJavaVM is called, which initializes a whole tonne of stuff including all the main class libraries so that we attach the current native thread and have it form the initial java.lang.Thread. And then nothing happens. The execution of the main method of some class only happens by convention because the JLS/JVMS says that is what is to happen, but in reality any static method of any class can actually be invoked. It is the java launcher that implements what the JLS/JVMS says, by explicitly loading the initial class (via JNI) and then invoking its main method (again via JNI).
11-08-2022

Returning to an earlier matter, describing what the JVM does when a program exits -- "blocking" any daemon and non-daemon threads that have not yet exited, and then halting -- is something we can avoid. We can describe things solely in terms of the program's threads and code. This also frees us from having to say that the JVM halts, which is nice for two reasons: (1) the JVM does not meaningfully halt when a program exits due to sudden power-off, and (2) halting is presented by Runtime.exit as an intrinsic part of the shutdown sequence but actually seems to be an extrinsic decision by the launcher. From Stuart's investigation: "The behavior of shutting down when the number of non-daemon threads reaches zero for the first time only occurs as the result of some launcher calling DestroyJavaVM. The default launcher does this after Java's `main` method returns. I wrote an alternative launcher that does something else (native sleep) after Java's `main` method returns, and after all the non-daemon threads exited, nothing happened. *The VM was still running with nothing happening.*" Separately, it came to my attention that the javadoc for `Thread` says that a thread "terminates". It would be be best for the JLS to use this terminology, rather than saying that a thread "exits". Here is the revised 12.8 text: * 12.8 Program Exit A program consists of one or more threads of execution. A thread is either a non-daemon thread, a daemon thread, or a shutdown hook. Readers are referred to the API specifications of `Thread` and `Runtime` for details of how threads obtain daemon status, and how shutdown hooks are registered. A thread _terminates_ if either (i) its `run` method completes normally, or (ii) its `run` method completes abruptly and the relevant uncaught exception handler (11.3) completes normally or abruptly. With no code left to run, the thread has completed execution and therefore has no current method (JVMS 2.5.1). A program _exits_ when one of the following situations has occurred: 1. All of its non-daemon threads have terminated, and all of the shutdown hooks which consequently were started by the Java Virtual Machine, if any, have terminated. 2. A thread invoked System.exit or Runtime.exit, and all of the shutdown hooks which consequently were started by the Java Virtual Machine, if any, have terminated. 3. A thread invoked Runtime.halt. (No shutdown hooks are started in this situation.) 4. The Java Virtual Machine implementation recognized an external event as requesting termination of the Java Virtual Machine, and all of the shutdown hooks which consequently were started by the Java Virtual Machine, if any, have terminated. The nature of the event is outside the scope of this specification, but is necessarily something that a Java Virtual Machine implementation can handle reliably. An example is receiving a signal from the operating system. 5. An external event occurred that the Java Virtual Machine implementation cannot handle. (No shutdown hooks are started in this situation.) The nature of the event is outside the scope of this specification, but is necessarily something that a Java Virtual Machine implementation cannot recognize or recover from in any way. Examples include a fatal error occurring in the process running the implementation, or power being removed from the computer running the implementation. Note that, in any situation where shutdown hooks are started, program exit depends on termination of those shutdown hooks. The rationale is as follows. When the number of non-daemon threads drops to zero or a thread invokes Runtime.exit/System.exit, it is likely that the program has no more work to do and is transitioning toward exit; however, the program may still have other threads that are performing ancillary tasks, and it would be undesirable to "stop them in their tracks". Shutdown hooks let the program gracefully interrupt and "bring down" such threads in an application-specific manner; accordingly, the program has not yet exited if shutdown hooks are still running. Upon program exit, any daemon or non-daemon thread that has not yet terminated will execute no further Java code. Its current method does not complete normally or abruptly. No `finally` clause of any method in the thread is executed, nor any uncaught exception handler. If program exit occurs because a thread invoked `Runtime.halt` _while shutdown hooks were running_, then, in addition to daemon and non-daemon threads, any shutdown hook that has not yet terminated will execute no further Java code. Native applications can use the JNI Invocation API to create and destroy the Java Virtual Machine in such a way that a Java program, having started execution in the `main` method of an initial class (12.1), exits as described in the first situation above.
10-08-2022

Re: "termination due to the last non-daemon thread exiting is a feature of the JLS not the JVMS" -- the JLS does _not_ say that the JVM terminates when the last non-daemon thread terminates. (Terminology note: I changed "a thread exits if ..." to "a thread terminates if ..." for alignment with java.lang.Thread.) The JLS says that the program exits when the last non-daemon thread terminates -- you can't tell what happened to the JVM. The JLS is not tied to the JNI Invocation API, and the JNI Invocation API is not tied to the JLS -- it just so happens that the JLS idea of "program exit" has a shape in common with DestroyJavaVM, suggesting that a JLS implementer (!= a JVMS implementer) can profitably use the JNI Invocation API. Re: "Non-Java-language VM uses, that use the JNI invocation API, are relying on JLS semantics not JVM" -- not so. Suppose the Foobar Language Specification (FLS) says that a Foobar program exits when the last non-daemon thread terminates -- how does the FLS implementer implement that on a Java runtime? They know from JVMS 5.7 that the JVM terminates when Runtime.exit and Runtime.halt are invoked, but not when the last non-daemon thread terminates. Fortunately, the FLS implementer learns about the JNI Invocation API and realizes that DestroyJavaVM will be helpful for achieving the semantics of Foobar program exit. The FLS implementer is relying on the JNI Invocation API, but never the JLS.
09-08-2022

PS. Each time you edit the comment I have to re-read the entire thing wondering what has changed. Is there some way this can be captured in a review tool? Thanks.
09-08-2022

Okay ... that is somewhat subtle and I'm a little uncomfortable that termination due to the last non-daemon thread exiting is a feature of the JLS not the JVMS as it suggests to me that non-Java-language VM uses, that use the JNI invocation API, are relying on JLS semantics not JVM. They probably don't care but it still "smells" to me from a spec perspective. In essence the JVMS doesn't specify any way to actually create or a destroy a VM, it just describes how a VM should operate if you can get your hands on one. The JNI spec is the only thing that says how to create and destroy and VM, and its semantics are tied to the JLS.
09-08-2022

FYI the last paragraph about the JNI Invocation API aligns with a paragraph proposed for JVMS 5.7 by JDK-8290388. Anyone who compares the five situations listed in JLS 12.8 with the four situations listed in JVMS 5.7 will realize that when the last non-daemon thread terminates, the program exits but the JVM does not terminate. The paragraph proposed for JVMS 5.7 spells out that "The Java Virtual Machine does not terminate "automatically" when the last non-daemon thread terminates."
08-08-2022

Just to tie in with feedback I gave on Stuart's Runtime updates, The last paragraph about the invocation API neatly ties the termination semantics to the paired use of CreateJavaVM and DestroyJavaVM, with no suggestion that you might somehow get a VM to terminate when the last non-daemon thread terminates, without DestroyJavaVM being called.
08-08-2022

That's a thumbs up from me. :)
18-07-2022

Following on from Stuart's comment, let me record some additional color for the sake of easy reference: "If the thread executing the `main` method of the initial class fires off a nonzero number of non-daemon threads and then exits, the `java` launcher blocks in DestroyJavaVM *which waits for those all non-daemon threads to exit, then starts shutdown*. Alternatively, If the thread executing `main` does a bunch of work, possibly creating and joining threads, and itself ends up being the last non-daemon thread, then when it finally exits the launcher calls DestroyJavaVM; *since there are no other non-daemon threads at this point, shutdown proceeds immediately.*" We now have a clear picture: the #1 bullet specifies the right thing, and a launcher can straightforwardly use the JNI Invocation API to do the thing right. I will drop #4 and instead have a note acknowledging the utility of the JNI Invocation API.
15-07-2022

OK, I did some background analysis. Regarding >> 1. All of its non-daemon threads have exited, and all of the shutdown hooks which consequently were started by the Java Virtual Machine, if any, have exited. >> 4. A thread invoked JNI_DestroyJavaVM from native code, and all other non-daemon threads have exited, [...] and David's comment, > These are not really separate cases, and the "program exiting" is actually an explicit distinct step. I had thought that exit of a non-daemon thread would decrement a counter, which upon reaching zero would spontaneously initiate a shutdown. However I now see that shutdown-when-non-daemon-thread-count-reaches-zero relies on some native code somewhere -- typically a "launcher" -- to call DestroyJavaVM in order to get this behavior. The question is whether this behavior ought to be mandated in the JLS. Not having it seems to leave a gap. It certainly is long-standing behavior in the contexts we're familiar with (Java SE on desktop/server machines). Having this behavior in the JLS implies that there's some responsibility on launcher implementations to be implemented correctly for conformance, and also that it's pretty easy to write a launcher that is non-conformant. In fact, it's so easy that I wrote one. :-) I don't think this is bad, though, since conformance has always been a responsibility shared across several components. In any case it looks like cases 1 and 4 can be combined.
15-07-2022

3. From the perspective of the Java program, it's not material whether the Java Virtual Machine was started by a "launcher" using the JNI Invocation API to create a "process", or started by the hardware at power-on as the sole execution environment, or something else. JLS 12.1 specifies that the Java Virtual Machine starts execution of a program by invoking `main` in some class, but does not specify how the Java Virtual Machine itself was started. At the other end, JLS 12.8 has to specify how a program exits within the environment provided by the Java Virtual Machine (and then observe that the JVM "halts"), but should not specify anything about launchers, processes, etc. Logically, #4 is a subset of #1 in the case where the Java Virtual Machine was started via the JNI Invocation API, and I thought about presenting #4 in that fashion, but I think there is pedagogical value in having #1 and #4 as siblings. (For two reasons: (1) their common sentence structure shows that a Java program experiences similar exit behavior in different scenarios, and (2) the JLS's job is to give a 360-degree view of how different parts of the JVM's execution environment -- Java threads, the Java API's "shutdown hooks", the JNI Invocation API -- affect a Java program as it exits. Developers want complete, connected explanations of what they can expect for their program.) 4. Here's a copy-paste of the first paragraph of https://download.java.net/java/early_access/jdk19/docs/specs/jni/invocation.html#terminating-the-vm -- "The JNI_DestroyJavaVM() function terminates a Java VM." (I also note that specs/jni/index.html shows Chapter 5 as having a section called "Unloading the VM", but specs/jni/invocation.html actually calls the section "Terminating the VM".)
14-07-2022

> I still wish to spell out how a thread that is blocked is non-functional, e.g., no `finally` clauses run (people have asked about this), I find such questions normally stem from a misconception that somehow the thread is terminated by a special exception. If it is explained that the thread becomes blocked mid-execution and makes no further progress then questions about future running of finally clauses, or method completion are moot. To mention such things, to me, introduces more potential for confusion as I would wonder why you make mention of such things for a thread that is not executing. > the thread will not execute any further code The thread will not execute any further _Java_ code. It can still execute native code. > 3. To clarify, a program exits when _one_ of the following occurs: ... You cannot start a JVM without using the invocation API hence cases 1 and 4 are the same. You are either using the java launcher to do it, or else your own "launcher" but the process is the same. > The program that exits is the Java program executed by the JVM Okay but this still gets imprecise because the Java thread created by the Java program can continue to run, executing native code, after the "program" has "exited" (but not after the process has exited). > 4. I looked at https://docs.oracle.com/en/java/javase/18/docs/specs/jni/invocation.html#terminating-the-vm and was struck by the omission of text about shutdown hooks. The JNI spec should not be too involved in the details of what happens on the Java side: shutdown hooks are a JDK detail. Where the JNI spec says "Terminates the operation of the JVM .." that should be read as "Terminates the operation of the JVM by initiating the JVM shutdown sequence ..." whatever that may involve. And if "shutdown sequence" is well-defined then we can change it to actually say that, with a suitable JLS cross-reference. > (Is the function called `JNI_DestroyJavaVM` or `DestroyJavaVM`? Both names are used.) Are you sure the underscore is present? We refer to the "JNI DestroyJavaVM" function as opposed to say the (non-existent) "JVMTI DestroyJavaVM" function, but the actual function name is just DestroyJavaVM.
14-07-2022

3. To clarify, a program exits when _one_ of the following occurs: <#1, pertaining to a JVM started without the JNI Invocation API>, or ..., or <#4, pertaining to a JVM started with the JNI Invocation API>, or ... The program that exits is the Java program executed by the JVM, and not (in the case of #4) the C program which started the JVM with JNI CreateJavaVM and is now terminating the JVM with JNI DestroyJavaVM. The behavior of the C program after it calls DestroyJavaVM is not 12.8's concern. 4. I looked at https://docs.oracle.com/en/java/javase/18/docs/specs/jni/invocation.html#terminating-the-vm and was struck by the omission of text about shutdown hooks. By contrast, see how the spec of Runtime.exit spells out what happens with shutdown hooks (jointly with the spec of Runtime.addShutdownHook), and then 12.8 gives an overview of that scenario in bullet #2. 12.8 bullet #4 should not be the only part of the Java SE Platform Spec that relates DestroyJavaVM to shutdown hooks. (Is the function called `JNI_DestroyJavaVM` or `DestroyJavaVM`? Both names are used.)
13-07-2022

1. Thanks for the observations on "stopping" a thread. I agree that people will think of Thread.stop and get sidetracked. I like the idea of the JVM "blocking" execution of a thread. I still wish to spell out how a thread that is blocked is non-functional, e.g., no `finally` clauses run (people have asked about this), and e.g., the current method does not complete (for contrast with 12.8's second normative paragraph, where a thread that exits has no current method at all). "Upon program exit, the Java Virtual Machine blocks the execution of any daemon and non-daemon threads that have not yet exited, and then halts. When a thread's execution is blocked by the Java Virtual Machine, the thread will not execute any further code. Its current method does not complete normally or abruptly. No `finally` clause of any method in the thread is executed, nor any uncaught exception handler." 2. I agree it's right to avoid specifying "The run-time data areas of the thread are destroyed." as a positive action at JVM shutdown. The same sentence appears in the second normative paragraph, about thread exit, to comport with a sentence in JVMS 2.5 ("Per-thread data areas are created when a thread is created and destroyed when the thread exits." https://docs.oracle.com/javase/specs/jvms/se18/html/jvms-2.html#jvms-2.5-100) ... but it doesn't add much value at thread exit, and it no longer aligns with itself at thread-blocked-by-the-JVM, so I will remove it from the thread exit paragraph too.
13-07-2022

> Upon program exit, the Java Virtual Machine _stops_ any daemon and non-daemon threads that have not yet exited, and then halts. Unfortunately people will think Thread.stop() when they read _stop_ here. The mechanism by which threads are "stopped" is simply to force them to block on a condition that will never be satisfied; and that isn't even true completely as threads executing native code will continue to do so right up until they either try to resume execution of Java code, or the process terminates. Suggestion: "Upon program exit, the Java Virtual Machine blocks the execution of any daemon and non-daemon threads that have not yet exited, such that they will not execute any further Java instructions, and then halts. " You then do not need the paragraph "Stopping a thread ...". > The run-time data areas of the thread are destroyed. No run-time data areas of threads need be destroyed when the VM exits other than implicitly by the whole process exiting. However the process does not need to exit for the VM to exit. If DestroyJavaVM is called from a process hosting the VM then all non-terminated threads are still fully operational from an OS perspective, they are simply all blocked (at a safepoint). Their resources remain part of the process. In theory a VM could actually force threads to terminate and reclaim their data areas but there is no specification that requires an implementation to do this. So it may be better just to delete that sentence too.
13-07-2022

> 1. All of its non-daemon threads have exited, and all of the shutdown hooks which consequently were started by the Java Virtual Machine, if any, have exited. > 4. A thread invoked JNI_DestroyJavaVM from native code, and all other non-daemon threads have exited, and all of the shutdown hooks which consequently were started by the Java Virtual Machine, if any, have exited. These are not really separate cases, and the "program exiting" is actually an explicit distinct step. A program creates the JVM, via JNI CreateJavaVM, and then later can quiesce the JVM, when all non-daemon threads (and shutdown hooks) are complete, using JNI DestroyJavaVM. The program can then choose to whether to exit or not.
13-07-2022