《Head First Java》读书笔记(一)
Chapter 1 Breaking the Surface
The way Java works
What you’ll do in Java
Source code –> Complier –> Bytecode –> Virtual Machine
A very brief history of Java
Code structure in Java
Source file (源文件)
- A source code file (with the .java extension) holds one class defini- tion. The class represents a piece of your program, although a very tiny application might need just a single class. The class must go within a pair of curly braces.
Class (类)
-
A class has one or more methods. In the Dog class, the bark method will hold instructions for how the Dog should bark. Your methods must be declared inside a class
(in other words, within the curly braces of the class).
Method (方法)
- Within the curly braces of a method, write your instructions for how that method should be performed. Method code is basi- cally a set of statements, and for now you can think of a method kind of like a function or proce- dure.
Anatomy of a class
在这一小节的开头,有这样一段话:
When the JVM starts running, it looks for the class you give it at the com- mand line. Then it starts looking for a specially-written method that looks exactly like:
1 2 3
public static void main (String[] args) { // your code goes here }
Next, the JVM runs everything between the curly braces { } of your main method. Every Java application has to have at least one class, and at least one main method (not one main per class; just one main per application).
这不禁让我思考,Java 中的 main()
方法,是如何被 JVM 找到并执行的
Java中的 main()
方法是如何被执行的
找到三篇非常有参考价值的文章:
简单总结一下:
-
java.exe
大体上可以分为启动器部分和hotspot
部分。代码里的main()
方法肯定是由hotspot
调用的。 -
- 启动器负责执行一些命令行解析,环境初始化等任务,hotspot部分则是真正的虚拟机干活的地方。
-
最新的openJdk源码(src/java.base/share/native/launcher/main.c)是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
/* * Copyright (c) 1995, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * This file contains the main entry point into the launcher code * this is the only file which will be repeatedly compiled by other * tools. The rest of the files will be linked in. */ #include "defines.h" #include "jli_util.h" #include "jni.h" /* * Entry point. */ #ifdef JAVAW char **__initenv; int WINAPI WinMain(HINSTANCE inst, HINSTANCE previnst, LPSTR cmdline, int cmdshow) { int margc; char** margv; int jargc; char** jargv; const jboolean const_javaw = JNI_TRUE; __initenv = _environ; #else /* JAVAW */ JNIEXPORT int main(int argc, char **argv) { int margc; char** margv; int jargc; char** jargv; const jboolean const_javaw = JNI_FALSE; #endif /* JAVAW */ { int i, main_jargc, extra_jargc; JLI_List list; main_jargc = (sizeof(const_jargs) / sizeof(char *)) > 1 ? sizeof(const_jargs) / sizeof(char *) : 0; // ignore the null terminator index extra_jargc = (sizeof(const_extra_jargs) / sizeof(char *)) > 1 ? sizeof(const_extra_jargs) / sizeof(char *) : 0; // ignore the null terminator index if (main_jargc > 0 && extra_jargc > 0) { // combine extra java args jargc = main_jargc + extra_jargc; list = JLI_List_new(jargc + 1); for (i = 0 ; i < extra_jargc; i++) { JLI_List_add(list, JLI_StringDup(const_extra_jargs[i])); } for (i = 0 ; i < main_jargc ; i++) { JLI_List_add(list, JLI_StringDup(const_jargs[i])); } // terminate the list JLI_List_add(list, NULL); jargv = list->elements; } else if (extra_jargc > 0) { // should never happen fprintf(stderr, "EXTRA_JAVA_ARGS defined without JAVA_ARGS"); abort(); } else { // no extra args, business as usual jargc = main_jargc; jargv = (char **) const_jargs; } } JLI_InitArgProcessing(jargc > 0, const_disable_argfile); #ifdef _WIN32 { int i = 0; if (getenv(JLDEBUG_ENV_ENTRY) != NULL) { printf("Windows original main args:\n"); for (i = 0 ; i < __argc ; i++) { printf("wwwd_args[%d] = %s\n", i, __argv[i]); } } } JLI_CmdToArgs(GetCommandLine()); margc = JLI_GetStdArgc(); // add one more to mark the end margv = (char **)JLI_MemAlloc((margc + 1) * (sizeof(char *))); { int i = 0; StdArg *stdargs = JLI_GetStdArgs(); for (i = 0 ; i < margc ; i++) { margv[i] = stdargs[i].arg; } margv[i] = NULL; } #else /* *NIXES */ { // accommodate the NULL at the end JLI_List args = JLI_List_new(argc + 1); int i = 0; // Add first arg, which is the app name JLI_List_add(args, JLI_StringDup(argv[0])); // Append JDK_JAVA_OPTIONS if (JLI_AddArgsFromEnvVar(args, JDK_JAVA_OPTIONS)) { // JLI_SetTraceLauncher is not called yet // Show _JAVA_OPTIONS content along with JDK_JAVA_OPTIONS to aid diagnosis if (getenv(JLDEBUG_ENV_ENTRY)) { char *tmp = getenv("_JAVA_OPTIONS"); if (NULL != tmp) { JLI_ReportMessage(ARG_INFO_ENVVAR, "_JAVA_OPTIONS", tmp); } } } // Iterate the rest of command line for (i = 1; i < argc; i++) { JLI_List argsInFile = JLI_PreprocessArg(argv[i], JNI_TRUE); if (NULL == argsInFile) { JLI_List_add(args, JLI_StringDup(argv[i])); } else { int cnt, idx; cnt = argsInFile->size; for (idx = 0; idx < cnt; idx++) { JLI_List_add(args, argsInFile->elements[idx]); } // Shallow free, we reuse the string to avoid copy JLI_MemFree(argsInFile->elements); JLI_MemFree(argsInFile); } } margc = args->size; // add the NULL pointer at argv[argc] JLI_List_add(args, NULL); margv = args->elements; } #endif /* WIN32 */ return JLI_Launch(margc, margv, jargc, (const char**) jargv, 0, NULL, VERSION_STRING, DOT_VERSION, (const_progname != NULL) ? const_progname : *margv, (const_launcher != NULL) ? const_launcher : *margv, jargc > 0, const_cpwildcard, const_javaw, 0); }
-
JLI_Launch()
就是启动器函数。 -
大致顺序为:
java.base
—>JLI_Launcher()
–->JavaMain()
-
JavaMain()
可以理解为,启动JVM的函数。 -
代码如下:(位置:src/java.base/share/native/libjli/java.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
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 being launched jmethodID mainID; jobjectArray mainArgs; int ret = 0; jlong start = 0, end = 0; RegisterThread(); /* Initialize the virtual machine */ start = CurrentTimeMicros(); if (!InitializeJVM(&vm, &env, &ifn)) { JLI_ReportErrorMessage(JVM_ERROR1); exit(1); } if (showSettings != NULL) { ShowSettings(env, showSettings); CHECK_EXCEPTION_LEAVE(1); } // show resolved modules and continue if (showResolvedModules) { ShowResolvedModules(env); CHECK_EXCEPTION_LEAVE(1); } // list observable modules, then exit if (listModules) { ListModules(env); CHECK_EXCEPTION_LEAVE(1); LEAVE(); } // describe a module, then exit if (describeModule != NULL) { DescribeModule(env, describeModule); CHECK_EXCEPTION_LEAVE(1); LEAVE(); } if (printVersion || showVersion) { PrintJavaVersion(env); CHECK_EXCEPTION_LEAVE(0); if (printVersion) { LEAVE(); } } // modules have been validated at startup so exit if (validateModules) { LEAVE(); } /* If the user specified neither a class name nor a JAR file */ if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) { PrintUsage(env, printXUsage); CHECK_EXCEPTION_LEAVE(1); LEAVE(); } FreeKnownVMs(); /* after last possible PrintUsage */ if (JLI_IsTraceLauncher()) { end = CurrentTimeMicros(); JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n", (long)(end-start)); } /* At this stage, argc/argv have the application's arguments */ if (JLI_IsTraceLauncher()){ int i; printf("%s is '%s'\n", launchModeNames[mode], what); printf("App's argc is %d\n", argc); for (i=0; i < argc; i++) { printf(" argv[%2d] = '%s'\n", i, argv[i]); } } ret = 1; /* * Get the application's main class. It also checks if the main * method exists. * * See bugid 5030265. The Main-Class name has already been parsed * from the manifest, but not parsed properly for UTF-8 support. * Hence the code here ignores the value previously extracted and * uses the pre-existing code to reextract the value. This is * possibly an end of release cycle expedient. However, it has * also been discovered that passing some character sets through * the environment has "strange" behavior on some variants of * Windows. Hence, maybe the manifest parsing code local to the * launcher should never be enhanced. * * Hence, future work should either: * 1) Correct the local parsing code and verify that the * Main-Class attribute gets properly passed through * all environments, * 2) Remove the vestages of maintaining main_class through * the environment (and remove these comments). * * This method also correctly handles launching existing JavaFX * applications that may or may not have a Main-Class manifest entry. */ mainClass = LoadMainClass(env, mode, what); CHECK_EXCEPTION_NULL_LEAVE(mainClass); /* * In some cases when launching an application that needs a helper, e.g., a * JavaFX application with no main method, the mainClass will not be the * applications own main class but rather a helper class. To keep things * consistent in the UI we need to track and report the application main class. */ appClass = GetApplicationClass(env); NULL_CHECK_RETURN_VALUE(appClass, -1); /* Build platform specific argument array */ mainArgs = CreateApplicationArgs(env, argv, argc); CHECK_EXCEPTION_NULL_LEAVE(mainArgs); if (dryRun) { ret = 0; LEAVE(); } /* * PostJVMInit uses the class name as the application name for GUI purposes, * for example, on OSX this sets the application name in the menu bar for * both SWT and JavaFX. So we'll pass the actual application class here * instead of mainClass as that may be a launcher or helper class instead * of the application class. */ PostJVMInit(env, appClass, vm); CHECK_EXCEPTION_LEAVE(1); /* * The LoadMainClass not only loads the main class, it will also ensure * that the main method's signature is correct, therefore further checking * is not required. The main method is invoked here so that extraneous java * stacks are not in the application stack trace. */ mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V"); CHECK_EXCEPTION_NULL_LEAVE(mainID); /* Invoke main method. */ (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs); /* * The launcher's exit code (in the absence of calls to * System.exit) will be non-zero if main threw an exception. */ ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1; LEAVE(); } /* * Loads a class and verifies that the main class is present and it is ok to * call it for more details refer to the java implementation. */ static jclass LoadMainClass(JNIEnv *env, int mode, char *name) { jmethodID mid; jstring str; jobject result; jlong start = 0, end = 0; jclass cls = GetLauncherHelperClass(env); NULL_CHECK0(cls); if (JLI_IsTraceLauncher()) { start = CurrentTimeMicros(); } NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls, "checkAndLoadMain", "(ZILjava/lang/String;)Ljava/lang/Class;")); NULL_CHECK0(str = NewPlatformString(env, name)); NULL_CHECK0(result = (*env)->CallStaticObjectMethod(env, cls, mid, USE_STDERR, mode, str)); if (JLI_IsTraceLauncher()) { end = CurrentTimeMicros(); printf("%ld micro seconds to load main class\n", (long)(end-start)); printf("----%s----\n", JLDEBUG_ENV_ENTRY); } return (jclass)result; } static jclass GetApplicationClass(JNIEnv *env) { jmethodID mid; jclass appClass; jclass cls = GetLauncherHelperClass(env); NULL_CHECK0(cls); NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls, "getApplicationClass", "()Ljava/lang/Class;")); appClass = (*env)->CallStaticObjectMethod(env, cls, mid); CHECK_EXCEPTION_RETURN_VALUE(0); return appClass; } jclass GetLauncherHelperClass(JNIEnv *env) { if (helperClass == NULL) { NULL_CHECK0(helperClass = FindBootStrapClass(env, "sun/launcher/LauncherHelper")); } return helperClass; }
-
核心在于
117
行的LoadMainClass()
,可以参考注释。 -
153
行则是调用main()
方法。 -
基本步骤为:
- 调用
InitializeJVM()
函数初始化JVM。 - 调用
LoadMainClass()
函数获取Java程序的启动类。 - 调用
GetStaticMethodId()
函数查找Java启动方法,其实就是main()
方法。 - 调用
JNIEnv
中定义的CallStaticVoidMethod()
方法,最终会调用JavaCalls::call()
函数执行启动类中的main()
方法。
- 调用
Writing a class with a main
Java 程序始终由 main()
方法开始。
What can you say in the main method?
do something
- Statements: declarations, assignments, method calls, etc.
do something again and again
- Loops: for and while
do something under this condition
- Branching: if/else tests
Syntax Fun
- Each statement must end in a semicolon.
- A single-line comment begins with two forward slashes.
- Most white space doesn’t matter.
- Variables are declared with a name and a type.
- Classes and methods must be defined within a pair of curly braces.