Inside the Java Launcher: How the JVM Starts and Executes Main
This article walks through the native entry point of the Java launcher, explaining how the main function delegates to JLI_Launch, initializes the JVM, loads the main class, constructs arguments, and finally invokes the Java main method with detailed code excerpts.
Program Entry Point
The launcher starts in src/java.base/share/native/launcher/main.c, where the main function immediately calls JLI_Launch() located in src/java.base/share/native/libjli/java.c. This function is the bridge between the operating system and the Java Virtual Machine.
java.c – JLI_Launch()
JavaMain()is the native entry point that performs the JVM initialization, discovers the Java program’s main class and its main method, and then invokes it via JNI. After the call returns, the JVM process terminates.
JavaMain() Initialization Steps
The function receives a JavaMainArgs structure, extracts argc, argv, launch mode, and other flags, then proceeds with the following high‑level steps:
Register the current thread.
Initialize the JVM by calling InitializeJVM().
Handle optional actions such as showing settings, resolved modules, module lists, or version information.
Validate command‑line arguments and print usage if necessary.
Optionally trace launch timing and argument values.
Key Code Walkthrough
int JavaMain(void *_args){
JavaMainArgs *args = (JavaMainArgs *) _args;
int argc = args->argc;
char **argv = args->argv;
int mode = args->mode;
char *what = args->what;
InvocationFunctions ifn = args->ifn;
JavaVM *vm = 0;
JNIEnv *env = 0;
jclass mainClass = NULL;
jclass appClass = NULL; // actual application class to launch
jmethodID mainID;
jobjectArray mainArgs;
int ret = 0;
jlong start, end;
RegisterThread();
/* Initialize the JVM */
start = CounterGet();
if (!InitializeJVM(&vm, &env, &ifn)) {
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1);
}
if (showSettings != NULL) {
ShowSettings(env, showSettings);
CHECK_EXCEPTION_LEAVE(1);
}
// ... (handling of resolved modules, module list, description, version, validation)
if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {
PrintUsage(env, printXUsage);
CHECK_EXCEPTION_LEAVE(1);
LEAVE();
}
FreeKnownVMs();
if (JLI_IsTraceLauncher()) {
end = CounterGet();
JLI_TraceLauncher("%ld micro seconds to InitializeJVM
",
(long) (jint) Counter2Micros(end - start));
}
// At this point argc/argv contain the application arguments
if (JLI_IsTraceLauncher()) {
int i;
printf("%s is '%s'
", launchModeNames[mode], what);
printf("App's argc is %d
", argc);
for (i = 0; i < argc; i++) {
printf(" argv[%2d] = '%s'
", i, argv[i]);
}
}
ret = 1;
/* Load the main class and obtain its main method */
mainClass = LoadMainClass(env, mode, what);
CHECK_EXCEPTION_NULL_LEAVE(mainClass);
appClass = GetApplicationClass(env);
NULL_CHECK_RETURN_VALUE(appClass, -1);
mainArgs = CreateApplicationArgs(env, argv, argc);
CHECK_EXCEPTION_NULL_LEAVE(mainArgs);
if (dryRun) {
ret = 0;
LEAVE();
}
PostJVMInit(env, appClass, vm);
CHECK_EXCEPTION_LEAVE(1);
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
LEAVE();
}Loading Main Class and Invoking main()
After the JVM is ready, LoadMainClass() resolves the class specified by the Main‑Class manifest entry (or the class name supplied on the command line). The code contains extensive comments about historic bugs (e.g., bug 5030265) and notes that future work should improve manifest parsing.
Once the class is loaded, GetApplicationClass() determines the real application class, which may differ for JavaFX helper launchers. The launcher then builds a platform‑specific argument array with CreateApplicationArgs() and, unless a dry‑run is requested, calls PostJVMInit() to set GUI‑related properties (e.g., the application name on macOS).
The static method ID for main is obtained via GetStaticMethodID, and the method is invoked with CallStaticVoidMethod. If the Java main throws an uncaught exception, the launcher returns a non‑zero exit code.
Future Work and Remarks
The comments highlight two main areas for improvement: fixing the native manifest‑parsing code to handle UTF‑8 correctly across all environments, and removing the legacy mechanism that stores main_class via environment variables. The code also notes that the current implementation can correctly handle JavaFX applications that may lack a Main‑Class entry.
The next article will dissect the InitializeJVM() function, which performs the low‑level allocation, mounting, and initialization of the virtual machine.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
JavaEdge
First‑line development experience at multiple leading tech firms; now a software architect at a Shanghai state‑owned enterprise and founder of Programming Yanxuan. Nearly 300k followers online; expertise in distributed system design, AIGC application development, and quantitative finance investing.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
