JVM系列(三):JVM创建过程解析
上两篇中梳理了整个java启动过程中,jvm大致是如何运行的。即厘清了我们认为的jvm的启动过程。但那里面仅为一些大致的东西,比如参数解析,验证,dll加载等等。把最核心的loadJavaVM()交给了一个dll或者so库。也就是真正的jvm我们并没有接触到,我们仅看了一个包装者或者是上层应用的实现。即我们仅是在jdk的角度看了下虚拟机,这需要更深入一点。
1. 回顾jvm加载框架
虽然jvm的加载核心并不在jdk中,但它确实没有自己的简易入口。也就是说jvm想要启动,还得依靠jdk. 所以,让我们回顾下jdk是如何带动jvm的?
1.1. java启动框架
自然是在 JLI_Launch 的入口查看了。
// share/bin/java.c
/*
* Entry point.
*/
int
JLI_Launch(int argc, char ** argv, /* main argc, argc */
int jargc, const char** jargv, /* java args */
int appclassc, const char** appclassv, /* app classpath */
const char* fullversion, /* full version defined */
const char* dotversion, /* dot version defined */
const char* pname, /* program name */
const char* lname, /* launcher name */
jboolean javaargs, /* JAVA_ARGS */
jboolean cpwildcard, /* classpath wildcard*/
jboolean javaw, /* windows-only javaw */
jint ergo /* ergonomics class policy */
)
{
int mode = LM_UNKNOWN;
char *what = NULL;
char *cpath = 0;
char *main_class = NULL;
int ret;
InvocationFunctions ifn;
jlong start, end;
char jvmpath[MAXPATHLEN];
char jrepath[MAXPATHLEN];
char jvmcfg[MAXPATHLEN];
_fVersion = fullversion;
_dVersion = dotversion;
_launcher_name = lname;
_program_name = pname;
_is_java_args = javaargs;
_wc_enabled = cpwildcard;
_ergo_policy = ergo;
// 初始化启动器
InitLauncher(javaw);
// 打印状态
DumpState();
// 跟踪调用启动
if (JLI_IsTraceLauncher()) {
int i;
printf("Command line args:\n");
for (i = 0; i < argc ; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
AddOption("-Dsun.java.launcher.diag=true", NULL);
}
/*
* Make sure the specified version of the JRE is running.
*
* There are three things to note about the SelectVersion() routine:
* 1) If the version running isn't correct, this routine doesn't
* return (either the correct version has been exec'd or an error
* was issued).
* 2) Argc and Argv in this scope are *not* altered by this routine.
* It is the responsibility of subsequent code to ignore the
* arguments handled by this routine.
* 3) As a side-effect, the variable "main_class" is guaranteed to
* be set (if it should ever be set). This isn't exactly the
* poster child for structured programming, but it is a small
* price to pay for not processing a jar file operand twice.
* (Note: This side effect has been disabled. See comment on
* bugid 5030265 below.)
*/
// 解析命令行参数,选择一jre版本
SelectVersion(argc, argv, &main_class);
CreateExecutionEnvironment(&argc, &argv,
jrepath, sizeof(jrepath),
jvmpath, sizeof(jvmpath),
jvmcfg, sizeof(jvmcfg));
if (!IsJavaArgs()) {
// 设置一些特殊的环境变量
SetJvmEnvironment(argc,argv);
}
ifn.CreateJavaVM = 0;
ifn.GetDefaultJavaVMInitArgs = 0;
if (JLI_IsTraceLauncher()) {
start = CounterGet();
}
// 加载VM, 重中之重
if (!LoadJavaVM(jvmpath, &ifn)) {
return(6);
}
if (JLI_IsTraceLauncher()) {
end = CounterGet();
}
JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n",
(long)(jint)Counter2Micros(end-start));
++argv;
--argc;
// 解析更多参数信息
if (IsJavaArgs()) {
/* Preprocess wrapper arguments */
TranslateApplicationArgs(jargc, jargv, &argc, &argv);
if (!AddApplicationOptions(appclassc, appclassv)) {
return(1);
}
} else {
/* Set default CLASSPATH */
cpath = getenv("CLASSPATH");
if (cpath == NULL) {
cpath = ".";
}
SetClassPath(cpath);
}
/* Parse command line options; if the return value of
* ParseArguments is false, the program should exit.
*/
// 解析参数
if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))
{
return(ret);
}
/* Override class path if -jar flag was specified */
if (mode == LM_JAR) {
SetClassPath(what); /* Override class path */
}
/* set the -Dsun.java.command pseudo property */
SetJavaCommandLineProp(what, argc, argv);
/* Set the -Dsun.java.launcher pseudo property */
SetJavaLauncherProp();
/* set the -Dsun.java.launcher.* platform properties */
SetJavaLauncherPlatformProps();
// 进行jvm初始化操作,一般是新开一个线程,然后调用 JavaMain() 实现java代码的权力交接
return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}
以上就是java启动jvm的核心框架。和真正的jvm相关的两个:1. SelectVersion() 会查找系统中存在的jvm即jre版本,是否可以被当前使用,以及main_class的验证;2. 在初始化时会调用jvm的 CreateJavaVM()方法,进行jvm真正的创建交接,这是通过函数指针实现的;
具体两个相关操作需要分解下,因为这些过程还是略微复杂的。
1.2. jre的查找定位与验证
要运行jvm,首先就是要确定系统中是否安装了相应的jre环境,并确定版本是否正确。
// java.c
/*
* The SelectVersion() routine ensures that an appropriate version of
* the JRE is running. The specification for the appropriate version
* is obtained from either the manifest of a jar file (preferred) or
* from command line options.
* The routine also parses splash screen command line options and
* passes on their values in private environment variables.
*/
static void
SelectVersion(int argc, char **argv, char **main_class)
{
char *arg;
char **new_argv;
char **new_argp;
char *operand;
char *version = NULL;
char *jre = NULL;
int jarflag = 0;
int headlessflag = 0;
int restrict_search = -1; /* -1 implies not known */
manifest_info info;
char env_entry[MAXNAMELEN + 24] = ENV_ENTRY "=";
char *splash_file_name = NULL;
char *splash_jar_name = NULL;
char *env_in;
int res;
/*
* If the version has already been selected, set *main_class
* with the value passed through the environment (if any) and
* simply return.
*/
// _JAVA_VERSION_SET=
if ((env_in = getenv(ENV_ENTRY)) != NULL) {
if (*env_in != '\0')
*main_class = JLI_StringDup(env_in);
return;
}
/*
* Scan through the arguments for options relevant to multiple JRE
* support. For reference, the command line syntax is defined as:
*
* SYNOPSIS
* java [options] class [argument...]
*
* java [options] -jar file.jar [argument...]
*
* As the scan is performed, make a copy of the argument list with
* the version specification options (new to 1.5) removed, so that
* a version less than 1.5 can be exec'd.
*
* Note that due to the syntax of the native Windows interface
* CreateProcess(), processing similar to the following exists in
* the Windows platform specific routine ExecJRE (in java_md.c).
* Changes here should be reproduced there.
*/
new_argv = JLI_MemAlloc((argc + 1) * sizeof(char*));
new_argv[0] = argv[0];
new_argp = &new_argv[1];
argc--;
argv++;
while ((arg = *argv) != 0 && *arg == '-') {
if (JLI_StrCCmp(arg, "-version:") == 0) {
version = arg + 9;
} else if (JLI_StrCmp(arg, "-jre-restrict-search") == 0) {
restrict_search = 1;
} else if (JLI_StrCmp(arg, "-no-jre-restrict-search") == 0) {
restrict_search = 0;
} else {
if (JLI_StrCmp(arg, "-jar") == 0)
jarflag = 1;
/* deal with "unfortunate" classpath syntax */
if ((JLI_StrCmp(arg, "-classpath") == 0 || JLI_StrCmp(arg, "-cp") == 0) &&
(argc >= 2)) {
*new_argp++ = arg;
argc--;
argv++;
arg = *argv;
}
/*
* Checking for headless toolkit option in the some way as AWT does:
* "true" means true and any other value means false
*/
if (JLI_StrCmp(arg, "-Djava.awt.headless=true") == 0) {
headlessflag = 1;
} else if (JLI_StrCCmp(arg, "-Djava.awt.headless=") == 0) {
headlessflag = 0;
} else if (JLI_StrCCmp(arg, "-splash:") == 0) {
splash_file_name = arg+8;
}
*new_argp++ = arg;
}
argc--;
argv++;
}
if (argc <= 0) { /* No operand? Possibly legit with -[full]version */
operand = NULL;
} else {
argc--;
*new_argp++ = operand = *argv++;
}
while (argc-- > 0) /* Copy over [argument...] */
*new_argp++ = *argv++;
*new_argp = NULL;
/*
* If there is a jar file, read the manifest. If the jarfile can't be
* read, the manifest can't be read from the jar file, or the manifest
* is corrupt, issue the appropriate error messages and exit.
*
* Even if there isn't a jar file, construct a manifest_info structure
* containing the command line information. It's a convenient way to carry
* this data around.
*/
if (jarflag && operand) {
if ((res = JLI_ParseManifest(operand, &info)) != 0) {
if (res == -1)
JLI_ReportErrorMessage(JAR_ERROR2, operand);
else
JLI_ReportErrorMessage(JAR_ERROR3, operand);
exit(1);
}
/*
* Command line splash screen option should have precedence
* over the manifest, so the manifest data is used only if
* splash_file_name has not been initialized above during command
* line parsing
*/
if (!headlessflag && !splash_file_name && info.splashscreen_image_file_name) {
splash_file_name = info.splashscreen_image_file_name;
splash_jar_name = operand;
}
} else {
info.manifest_version = NULL;
info.main_class = NULL;
info.jre_version = NULL;
info.jre_restrict_search = 0;
}
/*
* Passing on splash screen info in environment variables
*/
if (splash_file_name && !headlessflag) {
char* splash_file_entry = JLI_MemAlloc(JLI_StrLen(SPLASH_FILE_ENV_ENTRY "=")+JLI_StrLen(splash_file_name)+1);
JLI_StrCpy(splash_file_entry, SPLASH_FILE_ENV_ENTRY "=");
JLI_StrCat(splash_file_entry, splash_file_name);
putenv(splash_file_entry);
}
if (splash_jar_name && !headlessflag) {
char* splash_jar_entry = JLI_MemAlloc(JLI_StrLen(SPLASH_JAR_ENV_ENTRY "=")+JLI_StrLen(splash_jar_name)+1);
JLI_StrCpy(splash_jar_entry, SPLASH_JAR_ENV_ENTRY "=");
JLI_StrCat(splash_jar_entry, splash_jar_name);
putenv(splash_jar_entry);
}
/*
* The JRE-Version and JRE-Restrict-Search values (if any) from the
* manifest are overwritten by any specified on the command line.
*/
if (version != NULL)
info.jre_version = version;
if (restrict_search != -1)
info.jre_restrict_search = restrict_search;
/*
* "Valid" returns (other than unrecoverable errors) follow. Set
* main_class as a side-effect of this routine.
*/
if (info.main_class != NULL)
*main_class = JLI_StringDup(info.main_class);
/*
* If no version selection information is found either on the command
* line or in the manifest, simply return.
*/
if (info.jre_version == NULL) {
JLI_FreeManifest();
JLI_MemFree(new_argv);
return;
}
/*
* Check for correct syntax of the version specification (JSR 56).
*/
if (!JLI_ValidVersionString(info.jre_version)) {
JLI_ReportErrorMessage(SPC_ERROR1, info.jre_version);
exit(1);
}
/*
* Find the appropriate JVM on the system. Just to be as forgiving as
* possible, if the standard algorithms don't locate an appropriate
* jre, check to see if the one running will satisfy the requirements.
* This can happen on systems which haven't been set-up for multiple
* JRE support.
*/
jre = LocateJRE(&info);
JLI_TraceLauncher("JRE-Version = %s, JRE-Restrict-Search = %s Selected = %s\n",
(info.jre_version?info.jre_version:"null"),
(info.jre_restrict_search?"true":"false"), (jre?jre:"null"));
if (jre == NULL) {
if (JLI_AcceptableRelease(GetFullVersion(), info.jre_version)) {
JLI_FreeManifest();
JLI_MemFree(new_argv);
return;
} else {
JLI_ReportErrorMessage(CFG_ERROR4, info.jre_version);
exit(1);
}
}
/*
* If I'm not the chosen one, exec the chosen one. Returning from
* ExecJRE indicates that I am indeed the chosen one.
*
* The private environment variable _JAVA_VERSION_SET is used to
* prevent the chosen one from re-reading the manifest file and
* using the values found within to override the (potential) command
* line flags stripped from argv (because the target may not
* understand them). Passing the MainClass value is an optimization
* to avoid locating, expanding and parsing the manifest extra
* times.
*/
if (info.main_class != NULL) {
if (JLI_StrLen(info.main_class) <= MAXNAMELEN) {
(void)JLI_StrCat(env_entry, info.main_class);
} else {
JLI_ReportErrorMessage(CLS_ERROR5, MAXNAMELEN);
exit(1);
}
}
(void)putenv(env_entry);
ExecJRE(jre, new_argv);
JLI_FreeManifest();
JLI_MemFree(new_argv);
return;
}
// jre的定位过程
// solaris/bin/java_md_common.c
/*
* This is the global entry point. It examines the host for the optimal
* JRE to be used by scanning a set of directories. The set of directories
* is platform dependent and can be overridden by the environment
* variable JAVA_VERSION_PATH.
*
* This routine itself simply determines the set of appropriate
* directories before passing control onto ProcessDir().
*/
char*
LocateJRE(manifest_info* info)
{
char *path;
char *home;
char *target = NULL;
char *dp;
char *cp;
/*
* Start by getting JAVA_VERSION_PATH
*/
if (info->jre_restrict_search) {
path = JLI_StringDup(system_dir);
} else if ((path = getenv("JAVA_VERSION_PATH")) != NULL) {
path = JLI_StringDup(path);
} else {
if ((home = getenv("HOME")) != NULL) {
path = (char *)JLI_MemAlloc(JLI_StrLen(home) + \
JLI_StrLen(system_dir) + JLI_StrLen(user_dir) + 2);
sprintf(path, "%s%s:%s", home, user_dir, system_dir);
} else {
path = JLI_StringDup(system_dir);
}
}
/*
* Step through each directory on the path. Terminate the scan with
* the first directory with an acceptable JRE.
*/
cp = dp = path;
while (dp != NULL) {
cp = JLI_StrChr(dp, (int)':');
if (cp != NULL)
*cp = '\0';
if ((target = ProcessDir(info, dp)) != NULL)
break;
dp = cp;
if (dp != NULL)
dp++;
}
JLI_MemFree(path);
return (target);
}
// 尝试执行jre, 以验证其是否有效
// solaris/bin/java_md_common.c
/*
* Given a path to a jre to execute, this routine checks if this process
* is indeed that jre. If not, it exec's that jre.
*
* We want to actually check the paths rather than just the version string
* built into the executable, so that given version specification (and
* JAVA_VERSION_PATH) will yield the exact same Java environment, regardless
* of the version of the arbitrary launcher we start with.
*/
void
ExecJRE(char *jre, char **argv)
{
char wanted[PATH_MAX];
const char* progname = GetProgramName();
const char* execname = NULL;
/*
* Resolve the real path to the directory containing the selected JRE.
*/
if (realpath(jre, wanted) == NULL) {
JLI_ReportErrorMessage(JRE_ERROR9, jre);
exit(1);
}
/*
* Resolve the real path to the currently running launcher.
*/
SetExecname(argv);
execname = GetExecName();
if (execname == NULL) {
JLI_ReportErrorMessage(JRE_ERROR10);
exit(1);
}
/*
* If the path to the selected JRE directory is a match to the initial
* portion of the path to the currently executing JRE, we have a winner!
* If so, just return.
*/
if (JLI_StrNCmp(wanted, execname, JLI_StrLen(wanted)) == 0)
return; /* I am the droid you were looking for */
/*
* This should never happen (because of the selection code in SelectJRE),
* but check for "impossibly" long path names just because buffer overruns
* can be so deadly.
*/
if (JLI_StrLen(wanted) + JLI_StrLen(progname) + 6 > PATH_MAX) {
JLI_ReportErrorMessage(JRE_ERROR11);
exit(1);
}
/*
* Construct the path and exec it.
*/
(void)JLI_StrCat(JLI_StrCat(wanted, "/bin/"), progname);
argv[0] = JLI_StringDup(progname);
if (JLI_IsTraceLauncher()) {
int i;
printf("ReExec Command: %s (%s)\n", wanted, argv[0]);
printf("ReExec Args:");
for (i = 1; argv[i] != NULL; i++)
printf(" %s", argv[i]);
printf("\n");
}
JLI_TraceLauncher("TRACER_MARKER:About to EXEC\n");
(void)fflush(stdout);
(void)fflush(stderr);
execv(wanted, argv);
JLI_ReportErrorMessageSys(JRE_ERROR12, wanted);
exit(1);
}
接下来有个环境准备的过程,有点复杂。主要就是根据不同的平台要求,设置一些环境变量,想看更多的同学自行展开。
void
CreateExecutionEnvironment(int *pargc, char ***pargv,
char jrepath[], jint so_jrepath,
char jvmpath[], jint so_jvmpath,
char jvmcfg[], jint so_jvmcfg) {
/*
* First, determine if we are running the desired data model. If we
* are running the desired data model, all the error messages
* associated with calling GetJREPath, ReadKnownVMs, etc. should be
* output. However, if we are not running the desired data model,
* some of the errors should be suppressed since it is more
* informative to issue an error message based on whether or not the
* os/processor combination has dual mode capabilities.
*/
jboolean jvmpathExists;
/* Compute/set the name of the executable */
SetExecname(*pargv);
/* Check data model flags, and exec process, if needed */
{
char *arch = (char *)GetArch(); /* like sparc or sparcv9 */
char * jvmtype = NULL;
int argc = *pargc;
char **argv = *pargv;
int running = CURRENT_DATA_MODEL;
int wanted = running; /* What data mode is being
asked for? Current model is
fine unless another model
is asked for */
#ifdef SETENV_REQUIRED
jboolean mustsetenv = JNI_FALSE;
char *runpath = NULL; /* existing effective LD_LIBRARY_PATH setting */
char* new_runpath = NULL; /* desired new LD_LIBRARY_PATH string */
char* newpath = NULL; /* path on new LD_LIBRARY_PATH */
char* lastslash = NULL;
char** newenvp = NULL; /* current environment */
#ifdef __solaris__
char* dmpath = NULL; /* data model specific LD_LIBRARY_PATH,
Solaris only */
#endif /* __solaris__ */
#endif /* SETENV_REQUIRED */
char** newargv = NULL;
int newargc = 0;
/*
* Starting in 1.5, all unix platforms accept the -d32 and -d64
* options. On platforms where only one data-model is supported
* (e.g. ia-64 Linux), using the flag for the other data model is
* an error and will terminate the program.
*/
{ /* open new scope to declare local variables */
int i;
newargv = (char **)JLI_MemAlloc((argc+1) * sizeof(char*));
newargv[newargc++] = argv[0];
/* scan for data model arguments and remove from argument list;
last occurrence determines desired data model */
for (i=1; i < argc; i++) {
if (JLI_StrCmp(argv[i], "-J-d64") == 0 || JLI_StrCmp(argv[i], "-d64") == 0) {
wanted = 64;
continue;
}
if (JLI_StrCmp(argv[i], "-J-d32") == 0 || JLI_StrCmp(argv[i], "-d32") == 0) {
wanted = 32;
continue;
}
newargv[newargc++] = argv[i];
if (IsJavaArgs()) {
if (argv[i][0] != '-') continue;
} else {
if (JLI_StrCmp(argv[i], "-classpath") == 0 || JLI_StrCmp(argv[i], "-cp") == 0) {
i++;
if (i >= argc) break;
newargv[newargc++] = argv[i];
continue;
}
if (argv[i][0] != '-') { i++; break; }
}
}
/* copy rest of args [i .. argc) */
while (i < argc) {
newargv[newargc++] = argv[i++];
}
newargv[newargc] = NULL;
/*
* newargv has all proper arguments here
*/
argc = newargc;
argv = newargv;
}
/* If the data model is not changing, it is an error if the
jvmpath does not exist */
if (wanted == running) {
/* Find out where the JRE is that we will be using. */
if (!GetJREPath(jrepath, so_jrepath, arch, JNI_FALSE) ) {
JLI_ReportErrorMessage(JRE_ERROR1);
exit(2);
}
JLI_Snprintf(jvmcfg, so_jvmcfg, "%s%slib%s%s%sjvm.cfg",
jrepath, FILESEP, FILESEP, arch, FILESEP);
/* Find the specified JVM type */
if (ReadKnownVMs(jvmcfg, JNI_FALSE) < 1) {
JLI_ReportErrorMessage(CFG_ERROR7);
exit(1);
}
jvmpath[0] = '\0';
jvmtype = CheckJvmType(pargc, pargv, JNI_FALSE);
if (JLI_StrCmp(jvmtype, "ERROR") == 0) {
JLI_ReportErrorMessage(CFG_ERROR9);
exit(4);
}
if (!GetJVMPath(jrepath, jvmtype, jvmpath, so_jvmpath, arch, 0 )) {
JLI_ReportErrorMessage(CFG_ERROR8, jvmtype, jvmpath);
exit(4);
}
/*
* we seem to have everything we need, so without further ado
* we return back, otherwise proceed to set the environment.
*/
#ifdef SETENV_REQUIRED
mustsetenv = RequiresSetenv(wanted, jvmpath);
JLI_TraceLauncher("mustsetenv: %s\n", mustsetenv ? "TRUE" : "FALSE");
if (mustsetenv == JNI_FALSE) {
JLI_MemFree(newargv);
return;
}
#else
JLI_MemFree(newargv);
return;
#endif /* SETENV_REQUIRED */
} else { /* do the same speculatively or exit */
#ifdef DUAL_MODE
if (running != wanted) {
/* Find out where the JRE is that we will be using. */
if (!GetJREPath(jrepath, so_jrepath, GetArchPath(wanted), JNI_TRUE)) {
/* give up and let other code report error message */
JLI_ReportErrorMessage(JRE_ERROR2, wanted);
exit(1);
}
JLI_Snprintf(jvmcfg, so_jvmcfg, "%s%slib%s%s%sjvm.cfg",
jrepath, FILESEP, FILESEP, GetArchPath(wanted), FILESEP);
/*
* Read in jvm.cfg for target data model and process vm
* selection options.
*/
if (ReadKnownVMs(jvmcfg, JNI_TRUE) < 1) {
/* give up and let other code report error message */
JLI_ReportErrorMessage(JRE_ERROR2, wanted);
exit(1);
}
jvmpath[0] = '\0';
jvmtype = CheckJvmType(pargc, pargv, JNI_TRUE);
if (JLI_StrCmp(jvmtype, "ERROR") == 0) {
JLI_ReportErrorMessage(CFG_ERROR9);
exit(4);
}
/* exec child can do error checking on the existence of the path */
jvmpathExists = GetJVMPath(jrepath, jvmtype, jvmpath, so_jvmpath, GetArchPath(wanted), 0);
#ifdef SETENV_REQUIRED
mustsetenv = RequiresSetenv(wanted, jvmpath);
#endif /* SETENV_REQUIRED */
}
#else /* ! DUALMODE */
JLI_ReportErrorMessage(JRE_ERROR2, wanted);
exit(1);
#endif /* DUAL_MODE */
}
#ifdef SETENV_REQUIRED
if (mustsetenv) {
/*
* We will set the LD_LIBRARY_PATH as follows:
*
* o $JVMPATH (directory portion only)
* o $JRE/lib/$LIBARCHNAME
* o $JRE/../lib/$LIBARCHNAME
*
* followed by the user's previous effective LD_LIBRARY_PATH, if
* any.
*/
#ifdef __solaris__
/*
* Starting in Solaris 7, ld.so.1 supports three LD_LIBRARY_PATH
* variables:
*
* 1. LD_LIBRARY_PATH -- used for 32 and 64 bit searches if
* data-model specific variables are not set.
*
* 2. LD_LIBRARY_PATH_64 -- overrides and replaces LD_LIBRARY_PATH
* for 64-bit binaries.
*
* 3. LD_LIBRARY_PATH_32 -- overrides and replaces LD_LIBRARY_PATH
* for 32-bit binaries.
*
* The vm uses LD_LIBRARY_PATH to set the java.library.path system
* property. To shield the vm from the complication of multiple
* LD_LIBRARY_PATH variables, if the appropriate data model
* specific variable is set, we will act as if LD_LIBRARY_PATH had
* the value of the data model specific variant and the data model
* specific variant will be unset. Note that the variable for the
* *wanted* data model must be used (if it is set), not simply the
* current running data model.
*/
switch (wanted) {
case 0:
if (running == 32) {
dmpath = getenv("LD_LIBRARY_PATH_32");
wanted = 32;
} else {
dmpath = getenv("LD_LIBRARY_PATH_64");
wanted = 64;
}
break;
case 32:
dmpath = getenv("LD_LIBRARY_PATH_32");
break;
case 64:
dmpath = getenv("LD_LIBRARY_PATH_64");
break;
default:
JLI_ReportErrorMessage(JRE_ERROR3, __LINE__);
exit(1); /* unknown value in wanted */
break;
}
/*
* If dmpath is NULL, the relevant data model specific variable is
* not set and normal LD_LIBRARY_PATH should be used.
*/
if (dmpath == NULL) {
runpath = getenv("LD_LIBRARY_PATH");
} else {
runpath = dmpath;
}
#else /* ! __solaris__ */
/*
* If not on Solaris, assume only a single LD_LIBRARY_PATH
* variable.
*/
runpath = getenv("LD_LIBRARY_PATH");
#endif /* __solaris__ */
/* runpath contains current effective LD_LIBRARY_PATH setting */
jvmpath = JLI_StringDup(jvmpath);
new_runpath = JLI_MemAlloc(((runpath != NULL) ? JLI_StrLen(runpath) : 0) +
2 * JLI_StrLen(jrepath) + 2 * JLI_StrLen(arch) +
JLI_StrLen(jvmpath) + 52);
newpath = new_runpath + JLI_StrLen("LD_LIBRARY_PATH=");
/*
* Create desired LD_LIBRARY_PATH value for target data model.
*/
{
/* remove the name of the .so from the JVM path */
lastslash = JLI_StrRChr(jvmpath, '/');
if (lastslash)
*lastslash = '\0';
sprintf(new_runpath, "LD_LIBRARY_PATH="
"%s:"
"%s/lib/%s:"
"%s/../lib/%s",
jvmpath,
#ifdef DUAL_MODE
jrepath, GetArchPath(wanted),
jrepath, GetArchPath(wanted)
#else /* !DUAL_MODE */
jrepath, arch,
jrepath, arch
#endif /* DUAL_MODE */
);
/*
* Check to make sure that the prefix of the current path is the
* desired environment variable setting, though the RequiresSetenv
* checks if the desired runpath exists, this logic does a more
* comprehensive check.
*/
if (runpath != NULL &&
JLI_StrNCmp(newpath, runpath, JLI_StrLen(newpath)) == 0 &&
(runpath[JLI_StrLen(newpath)] == 0 || runpath[JLI_StrLen(newpath)] == ':') &&
(running == wanted) /* data model does not have to be changed */
#ifdef __solaris__
&& (dmpath == NULL) /* data model specific variables not set */
#endif /* __solaris__ */
) {
JLI_MemFree(newargv);
JLI_MemFree(new_runpath);
return;
}
}
/*
* Place the desired environment setting onto the prefix of
* LD_LIBRARY_PATH. Note that this prevents any possible infinite
* loop of execv() because we test for the prefix, above.
*/
if (runpath != 0) {
JLI_StrCat(new_runpath, ":");
JLI_StrCat(new_runpath, runpath);
}
if (putenv(new_runpath) != 0) {
exit(1); /* problem allocating memory; LD_LIBRARY_PATH not set
properly */
}
/*
* Unix systems document that they look at LD_LIBRARY_PATH only
* once at startup, so we have to re-exec the current executable
* to get the changed environment variable to have an effect.
*/
#ifdef __solaris__
/*
* If dmpath is not NULL, remove the data model specific string
* in the environment for the exec'ed child.
*/
if (dmpath != NULL)
(void)UnsetEnv((wanted == 32) ? "LD_LIBRARY_PATH_32" : "LD_LIBRARY_PATH_64");
#endif /* __solaris */
newenvp = environ;
}
#endif /* SETENV_REQUIRED */
{
char *newexec = execname;
#ifdef DUAL_MODE
/*
* If the data model is being changed, the path to the
* executable must be updated accordingly; the executable name
* and directory the executable resides in are separate. In the
* case of 32 => 64, the new bits are assumed to reside in, e.g.
* "olddir/LIBARCH64NAME/execname"; in the case of 64 => 32,
* the bits are assumed to be in "olddir/../execname". For example,
*
* olddir/sparcv9/execname
* olddir/amd64/execname
*
* for Solaris SPARC and Linux amd64, respectively.
*/
if (running != wanted) {
char *oldexec = JLI_StrCpy(JLI_MemAlloc(JLI_StrLen(execname) + 1), execname);
char *olddir = oldexec;
char *oldbase = JLI_StrRChr(oldexec, '/');
newexec = JLI_MemAlloc(JLI_StrLen(execname) + 20);
*oldbase++ = 0;
sprintf(newexec, "%s/%s/%s", olddir,
((wanted == 64) ? LIBARCH64NAME : ".."), oldbase);
argv[0] = newexec;
}
#endif /* DUAL_MODE */
JLI_TraceLauncher("TRACER_MARKER:About to EXEC\n");
(void) fflush(stdout);
(void) fflush(stderr);
#ifdef SETENV_REQUIRED
if (mustsetenv) {
execve(newexec, argv, newenvp);
} else {
execv(newexec, argv);
}
#else /* !SETENV_REQUIRED */
execv(newexec, argv);
#endif /* SETENV_REQUIRED */
JLI_ReportErrorMessageSys(JRE_ERROR4, newexec);
#ifdef DUAL_MODE
if (running != wanted) {
JLI_ReportErrorMessage(JRE_ERROR5, wanted, running);
#ifdef __solaris__
#ifdef __sparc
JLI_ReportErrorMessage(JRE_ERROR6);
#else /* ! __sparc__ */
JLI_ReportErrorMessage(JRE_ERROR7);
#endif /* __sparc */
#endif /* __solaris__ */
}
#endif /* DUAL_MODE */
}
exit(1);
}
}
1.3. 装载jvm链接库
经过前面的查找与验证,已经确认系统上有相应的jre环境了。但还没有进行真正的调用,这是重中之重。不过其实现却也是简单的,因为,它只是加载一个外部动态库而已。其主要目的在于获取与动态库的联系,直接些就是获取几个jvm的函数指针入口,以便后续可以调用。这和我们常说的面向接口编程,也一脉相承。
// 咱们就只看linux版本的实现好了,原理都一样,各平台实现不同而已(API规范不同)
// solaris/bin/java_md_solinux.c
jboolean
LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
{
void *libjvm;
JLI_TraceLauncher("JVM path is %s\n", jvmpath);
// jvmpath 是在前面解析出的地址, 直接加载打开即可获得
libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
if (libjvm == NULL) {
#if defined(__solaris__) && defined(__sparc) && !defined(_LP64) /* i.e. 32-bit sparc */
FILE * fp;
Elf32_Ehdr elf_head;
int count;
int location;
fp = fopen(jvmpath, "r");
if (fp == NULL) {
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
/* read in elf header */
count = fread((void*)(&elf_head), sizeof(Elf32_Ehdr), 1, fp);
fclose(fp);
if (count < 1) {
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
/*
* Check for running a server vm (compiled with -xarch=v8plus)
* on a stock v8 processor. In this case, the machine type in
* the elf header would not be included the architecture list
* provided by the isalist command, which is turn is gotten from
* sysinfo. This case cannot occur on 64-bit hardware and thus
* does not have to be checked for in binaries with an LP64 data
* model.
*/
if (elf_head.e_machine == EM_SPARC32PLUS) {
char buf[257]; /* recommended buffer size from sysinfo man
page */
long length;
char* location;
length = sysinfo(SI_ISALIST, buf, 257);
if (length > 0) {
location = JLI_StrStr(buf, "sparcv8plus ");
if (location == NULL) {
JLI_ReportErrorMessage(JVM_ERROR3);
return JNI_FALSE;
}
}
}
#endif
JLI_ReportErrorMessage(DLL_ERROR1, __LINE__);
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
// 加载jvm的目的,主要就是为了获取 JNI_CreateJavaVM,
// JNI_GetDefaultJavaVMInitArgs, JNI_GetCreatedJavaVMs 这些个指针
ifn->CreateJavaVM = (CreateJavaVM_t)
dlsym(libjvm, "JNI_CreateJavaVM");
if (ifn->CreateJavaVM == NULL) {
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
if (ifn->GetDefaultJavaVMInitArgs == NULL) {
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)
dlsym(libjvm, "JNI_GetCreatedJavaVMs");
if (ifn->GetCreatedJavaVMs == NULL) {
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
return JNI_TRUE;
}
可见装载jvm的过程显得很清晰明了,因为并没有做真正的调用,所以也只是算是处理初始化阶段。有简单的系统api提供,一切都很轻量级。
而jvm的真正创建,是在进行JvmInit()时,准备加载 main_class 时,才进行的的。
1.4. 回顾JavaMain执行框架
JavaMain是真正接入java代码的地方,它一般是会开启一个新线程去执行。前置调用可自行展开。
// 只看在linux中的实现
// solaris/bin/java_solinux.c
int
JVMInit(InvocationFunctions* ifn, jlong threadStackSize,
int argc, char **argv,
int mode, char *what, int ret)
{
ShowSplashScreen();
return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);
}
// java.c
int
ContinueInNewThread(InvocationFunctions* ifn, jlong threadStackSize,
int argc, char **argv,
int mode, char *what, int ret)
{
/*
* If user doesn't specify stack size, check if VM has a preference.
* Note that HotSpot no longer supports JNI_VERSION_1_1 but it will
* return its default stack size through the init args structure.
*/
if (threadStackSize == 0) {
struct JDK1_1InitArgs args1_1;
memset((void*)&args1_1, 0, sizeof(args1_1));
args1_1.version = JNI_VERSION_1_1;
ifn->GetDefaultJavaVMInitArgs(&args1_1); /* ignore return value */
if (args1_1.javaStackSize > 0) {
threadStackSize = args1_1.javaStackSize;
}
}
{ /* Create a new thread to create JVM and invoke main method */
JavaMainArgs args;
int rslt;
args.argc = argc;
args.argv = argv;
args.mode = mode;
args.what = what;
args.ifn = *ifn;
// 传入 JavaMain, 在新线程中调用
rslt = ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args);
/* If the caller has deemed there is an error we
* simply return that, otherwise we return the value of
* the callee
*/
return (ret != 0) ? ret : rslt;
}
}
// solaris/bin/java_md_solinux.c
/*
* Block current thread and continue execution in a new thread
*/
int
ContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) {
int rslt;
#ifdef __linux__
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
if (stack_size > 0) {
pthread_attr_setstacksize(&attr, stack_size);
}
// 常见的 pthread_xx 方式 创建线程
if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
void * tmp;
pthread_join(tid, &tmp);
rslt = (int)tmp;
} else {
/*
* Continue execution in current thread if for some reason (e.g. out of
* memory/LWP) a new thread can't be created. This will likely fail
* later in continuation as JNI_CreateJavaVM needs to create quite a
* few new threads, anyway, just give it a try..
*/
rslt = continuation(args);
}
pthread_attr_destroy(&attr);
#else /* ! __linux__ */
thread_t tid;
long flags = 0;
if (thr_create(NULL, stack_size, (void *(*)(void *))continuation, args, flags, &tid) == 0) {
void * tmp;
thr_join(tid, NULL, &tmp);
rslt = (int)tmp;
} else {
/* See above. Continue in current thread if thr_create() failed */
rslt = continuation(args);
}
#endif /* __linux__ */
return rslt;
}
JavaMain() 是执行java代码或者创建jvm的核心入口。
// share/bin/java.c
// 加载 main 函数类
// 通过引入 JavaMain(), 接入java方法
// #define JNICALL __stdcall
int JNICALL
JavaMain(void * _args)
{
JavaMainArgs *args = (JavaMainArgs *)_args;
int argc = args->argc;
char **argv = args->argv;
int mode = args->mode;
char *what = args->what;
// 一些jvm的调用实例,在之前的步骤中,通过加载相应动态链接方法,保存起来的
/**
* ifn->CreateJavaVM =
* (void *)GetProcAddress(handle, "JNI_CreateJavaVM");
* ifn->GetDefaultJavaVMInitArgs =
* (void *)GetProcAddress(handle, "JNI_GetDefaultJavaVMInitArgs");
*/
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, end;
// collector
RegisterThread();
/* Initialize the virtual machine */
start = CounterGet();
// 重点1:初始化jvm,失败则退出
// 此处会将重要变量 *env 进程初始化,从而使后续可用
if (!InitializeJVM(&vm, &env, &ifn)) {
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1);
}
// jvm检查完毕,如果只是一些展示类请求,则展示信息后,退出jvm
if (showSettings != NULL) {
ShowSettings(env, showSettings);
/**
* 宏是神奇的操作,此处 *env 直接引用
#define CHECK_EXCEPTION_LEAVE(CEL_return_value) \
do { \
if ((*env)->ExceptionOccurred(env)) { \
JLI_ReportExceptionDescription(env); \
ret = (CEL_return_value); \
LEAVE(); \
} \
} while (JNI_FALSE)
*/
CHECK_EXCEPTION_LEAVE(1);
}
// 调用 LEAVE() 方法的目的在于主动销毁jvm线程
// 且退出当前方法调用,即 LEAVE() 后方法不再被执行
/*
* Always detach the main thread so that it appears to have ended when
* the application's main method exits. This will invoke the
* uncaught exception handler machinery if main threw an
* exception. An uncaught exception handler cannot change the
* launcher's return code except by calling System.exit.
*
* Wait for all non-daemon threads to end, then destroy the VM.
* This will actually create a trivial new Java waiter thread
* named "DestroyJavaVM", but this will be seen as a different
* thread from the one that executed main, even though they are
* the same C thread. This allows mainThread.join() and
* mainThread.isAlive() to work as expected.
*/
/**
*
*
#define LEAVE() \
do { \
if ((*vm)->DetachCurrentThread(vm) != JNI_OK) { \
JLI_ReportErrorMessage(JVM_ERROR2); \
ret = 1; \
} \
if (JNI_TRUE) { \
(*vm)->DestroyJavaVM(vm); \
return ret; \
} \
} while (JNI_FALSE)
*/
if (printVersion || showVersion) {
PrintJavaVersion(env, showVersion);
CHECK_EXCEPTION_LEAVE(0);
if (printVersion) {
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 = CounterGet();
JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",
(long)(jint)Counter2Micros(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.
*
* 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.
*/
// 重点2:加载 main 指定的class类
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);
/*
* 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.
*/
// 加载main() 方法前执行初始化
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.
*/
// 重点3:执行 main(args[]) java方法
// 获取main()方法id, main(String[] args)
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
/* Build platform specific argument array */
// 构建args[] 参数
mainArgs = CreateApplicationArgs(env, argv, argc);
CHECK_EXCEPTION_NULL_LEAVE(mainArgs);
/* Invoke main method. */
// 调用java实现的main()方法
// XX:: 重要实现
(*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, end;
// sun/launcher/LauncherHelper
jclass cls = GetLauncherHelperClass(env);
NULL_CHECK0(cls);
if (JLI_IsTraceLauncher()) {
start = CounterGet();
}
// checkAndLoadMain(String) 方法作为中间main()调用
NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
"checkAndLoadMain",
"(ZILjava/lang/String;)Ljava/lang/Class;"));
str = NewPlatformString(env, name);
CHECK_JNI_RETURN_0(
result = (*env)->CallStaticObjectMethod(
env, cls, mid, USE_STDERR, mode, str));
if (JLI_IsTraceLauncher()) {
end = CounterGet();
printf("%ld micro seconds to load main class\n",
(long)(jint)Counter2Micros(end-start));
printf("----%s----\n", JLDEBUG_ENV_ENTRY);
}
return (jclass)result;
}
jclass
GetLauncherHelperClass(JNIEnv *env)
{
if (helperClass == NULL) {
NULL_CHECK0(helperClass = FindBootStrapClass(env,
"sun/launcher/LauncherHelper"));
}
return helperClass;
}
JavaMain 框架大概就是初始化创建jvm, 查找mainClass类, 找到函数指定, 构建args参数, 执行main(), 以及其他的一些兼容性处理。当JavaMain 执行完成时,则意味着整个jvm就完成了。所以,这也成为了我们要研究的重中之重。
2. 真正的jvm创建
jdk是我们看到的jvm前端,而背后的jre或者jvm才是大佬。它是在 JavaMain() 中触发调用的。也就是上节中看到的框架结构。初始化JVM的过程,实际就是调用jvm的函数指针 JNI_CreateJavaVM 地过程。
// 初始化jvm, 主要是调用 CreateJavaVM() 方法,进行创建jvm操作
/*
* Initializes the Java Virtual Machine. Also frees options array when
* finished.
*/
static jboolean
InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn)
{
JavaVMInitArgs args;
jint r;
memset(&args, 0, sizeof(args));
args.version = JNI_VERSION_1_2;
args.nOptions = numOptions;
args.options = options;
args.ignoreUnrecognized = JNI_FALSE;
if (JLI_IsTraceLauncher()) {
int i = 0;
printf("JavaVM args:\n ");
printf("version 0x%08lx, ", (long)args.version);
printf("ignoreUnrecognized is %s, ",
args.ignoreUnrecognized ? "JNI_TRUE" : "JNI_FALSE");
printf("nOptions is %ld\n", (long)args.nOptions);
for (i = 0; i < numOptions; i++)
printf(" option[%2d] = '%s'\n",
i, args.options[i].optionString);
}
// 转交给jvm执行
r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
JLI_MemFree(options);
return r == JNI_OK;
}
单是这 CreateJavaVM(), 就将jvm接入进来了。它的作用,就像是很多语言的 main() 入口一样,看似简单,却包罗万象。
2.1. jni.h文件概述
jni.h 中定义了许多的jdk可以调用的方法。比如上面提到 JNI_CreateJavaVM() 就是创建jvm的核心入口。在openjdk中,是在hotspot中实现的。
其中定义了各种java的类型,各种需要的接口。当我们想要自定义写一些native接口时,则jni.h是我们必须要引入的。其开头部分如下:
// share/vm/prims/jni.h
/*
* We used part of Netscape's Java Runtime Interface (JRI) as the starting
* point of our design and implementation.
*/
/******************************************************************************
* Java Runtime Interface
* Copyright (c) 1996 Netscape Communications Corporation. All rights reserved.
*****************************************************************************/
#ifndef _JAVASOFT_JNI_H_
#define _JAVASOFT_JNI_H_
#include
#include
/* jni_md.h contains the machine-dependent typedefs for jbyte, jint
and jlong */
#include "jni_md.h"
#ifdef __cplusplus
extern "C" {
#endif
/*
* JNI Types
*/
#ifndef JNI_TYPES_ALREADY_DEFINED_IN_JNI_MD_H
typedef unsigned char jboolean;
typedef unsigned short jchar;
typedef short jshort;
typedef float jfloat;
typedef double jdouble;
typedef jint jsize;
#ifdef __cplusplus
// c++版本的对象定义
class _jobject {};
class _jclass : public _jobject {};
class _jthrowable : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jobjectArray : public _jarray {};
typedef _jobject *jobject;
typedef _jclass *jclass;
typedef _jthrowable *jthrowable;
typedef _jstring *jstring;
typedef _jarray *jarray;
typedef _jbooleanArray *jbooleanArray;
typedef _jbyteArray *jbyteArray;
typedef _jcharArray *jcharArray;
typedef _jshortArray *jshortArray;
typedef _jintArray *jintArray;
typedef _jlongArray *jlongArray;
typedef _jfloatArray *jfloatArray;
typedef _jdoubleArray *jdoubleArray;
typedef _jobjectArray *jobjectArray;
#else
// c版本的对象定义
struct _jobject;
typedef struct _jobject *jobject;
typedef jobject jclass;
typedef jobject jthrowable;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jarray jobjectArray;
#endif
typedef jobject jweak;
// java各类型的定义简写
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
struct _jfieldID;
typedef struct _jfieldID *jfieldID;
struct _jmethodID;
typedef struct _jmethodID *jmethodID;
/* Return values from jobjectRefType */
typedef enum _jobjectType {
JNIInvalidRefType = 0,
JNILocalRefType = 1,
JNIGlobalRefType = 2,
JNIWeakGlobalRefType = 3
} jobjectRefType;
#endif /* JNI_TYPES_ALREADY_DEFINED_IN_JNI_MD_H */
/*
* jboolean constants
*/
#define JNI_FALSE 0
#define JNI_TRUE 1
/*
* possible return values for JNI functions.
*/
#define JNI_OK 0 /* success */
#define JNI_ERR (-1) /* unknown error */
#define JNI_EDETACHED (-2) /* thread detached from the VM */
#define JNI_EVERSION (-3) /* JNI version error */
#define JNI_ENOMEM (-4) /* not enough memory */
#define JNI_EEXIST (-5) /* VM already created */
#define JNI_EINVAL (-6) /* invalid arguments */
/*
* used in ReleaseScalarArrayElements
*/
#define JNI_COMMIT 1
#define JNI_ABORT 2
/*
* used in RegisterNatives to describe native method name, signature,
* and function pointer.
*/
typedef struct {
char *name;
char *signature;
void *fnPtr;
} JNINativeMethod;
...
大概就是兼容各平台,可能使用C实现,也可能使用C++实现。完整版本请参考官网: http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/6ea5a8067d1f/src/share/vm/prims/jni.h
其中,有两个比较重的结构体的定义:JNINativeInterface_ 是jni调用的大部分接口定义,基本上可以通过它调用任意方法。JNIEnv_ 是每个方法调用时的上下文管理器,它负责调用 JNINativeInterface_ 的方法,相当于是C++版本的JNINativeInterface_。
2.3. jvm核心创建框架
在jni.h中,还有很多C++或者C的判断,但对于CreateJavaVM这件事,就变成了一个纯粹C++的实现了。
// hotspot/src/share/vm/prims/jni.cpp
_JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {
#ifndef USDT2
HS_DTRACE_PROBE3(hotspot_jni, CreateJavaVM__entry, vm, penv, args);
#else /* USDT2 */
HOTSPOT_JNI_CREATEJAVAVM_ENTRY(
(void **) vm, penv, args);
#endif /* USDT2 */
jint result = JNI_ERR;
DT_RETURN_MARK(CreateJavaVM, jint, (const jint&)result);
// We're about to use Atomic::xchg for synchronization. Some Zero
// platforms use the GCC builtin __sync_lock_test_and_set for this,
// but __sync_lock_test_and_set is not guaranteed to do what we want
// on all architectures. So we check it works before relying on it.
#if defined(ZERO) && defined(ASSERT)
{
// java 魔术头
jint a = 0xcafebabe;
jint b = Atomic::xchg(0xdeadbeef, &a);
void *c = &a;
void *d = Atomic::xchg_ptr(&b, &c);
assert(a == (jint) 0xdeadbeef && b == (jint) 0xcafebabe, "Atomic::xchg() works");
assert(c == &b && d == &a, "Atomic::xchg_ptr() works");
}
#endif // ZERO && ASSERT
// At the moment it's only possible to have one Java VM,
// since some of the runtime state is in global variables.
// We cannot use our mutex locks here, since they only work on
// Threads. We do an atomic compare and exchange to ensure only
// one thread can call this method at a time
// We use Atomic::xchg rather than Atomic::add/dec since on some platforms
// the add/dec implementations are dependent on whether we are running
// on a multiprocessor, and at this stage of initialization the os::is_MP
// function used to determine this will always return false. Atomic::xchg
// does not have this problem.
if (Atomic::xchg(1, &vm_created) == 1) {
return JNI_EEXIST; // already created, or create attempt in progress
}
if (Atomic::xchg(0, &safe_to_recreate_vm) == 0) {
return JNI_ERR; // someone tried and failed and retry not allowed.
}
assert(vm_created == 1, "vm_created is true during the creation");
/**
* Certain errors during initialization are recoverable and do not
* prevent this method from being called again at a later time
* (perhaps with different arguments). However, at a certain
* point during initialization if an error occurs we cannot allow
* this function to be called again (or it will crash). In those
* situations, the 'canTryAgain' flag is set to false, which atomically
* sets safe_to_recreate_vm to 1, such that any new call to
* JNI_CreateJavaVM will immediately fail using the above logic.
*/
bool can_try_again = true;
// 核心: 创建vm
result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
if (result == JNI_OK) {
JavaThread *thread = JavaThread::current();
/* thread is thread_in_vm here */
*vm = (JavaVM *)(&main_vm);
// 将jvm信息存储到 penv 中,以备外部使用
*(JNIEnv**)penv = thread->jni_environment();
// Tracks the time application was running before GC
RuntimeService::record_application_start();
// Notify JVMTI
if (JvmtiExport::should_post_thread_life()) {
JvmtiExport::post_thread_start(thread);
}
EventThreadStart event;
if (event.should_commit()) {
event.set_javalangthread(java_lang_Thread::thread_id(thread->threadObj()));
event.commit();
}
#ifndef PRODUCT
#ifndef TARGET_OS_FAMILY_windows
#define CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(f) f()
#endif
// Check if we should compile all classes on bootclasspath
if (CompileTheWorld) ClassLoader::compile_the_world();
if (ReplayCompiles) ciReplay::replay(thread);
// Some platforms (like Win*) need a wrapper around these test
// functions in order to properly handle error conditions.
CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(test_error_handler);
CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(execute_internal_vm_tests);
#endif
// Since this is not a JVM_ENTRY we have to set the thread state manually before leaving.
ThreadStateTransition::transition_and_fence(thread, _thread_in_vm, _thread_in_native);
} else {
// 创建VM失败, 还原标识位信息
if (can_try_again) {
// reset safe_to_recreate_vm to 1 so that retrial would be possible
safe_to_recreate_vm = 1;
}
// Creation failed. We must reset vm_created
*vm = 0;
*(JNIEnv**)penv = 0;
// reset vm_created last to avoid race condition. Use OrderAccess to
// control both compiler and architectural-based reordering.
OrderAccess::release_store(&vm_created, 0);
}
return result;
}
看C++代码果然有点费劲,不过有着注释的加持,还算可以理解。大致就是测试环境,然后上CAS锁,保证vm加载时的线程安全性,进行vm创建,然后将vm的环境信息赋值给 外部 penv, 测试下vm有效性, 返回创建状态。
核心仍然是被包裹着的:Threads::create_vm()
// share/vm/runtime/thread.cpp
jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
extern void JDK_Version_init();
// Check version
if (!is_supported_jni_version(args->version)) return JNI_EVERSION;
// Initialize the output stream module
ostream_init();
// Process java launcher properties.
Arguments::process_sun_java_launcher_properties(args);
// Initialize the os module before using TLS
os::init();
// Initialize system properties.
Arguments::init_system_properties();
// So that JDK version can be used as a discrimintor when parsing arguments
JDK_Version_init();
// Update/Initialize System properties after JDK version number is known
Arguments::init_version_specific_system_properties();
// Parse arguments
jint parse_result = Arguments::parse(args);
if (parse_result != JNI_OK) return parse_result;
os::init_before_ergo();
jint ergo_result = Arguments::apply_ergo();
if (ergo_result != JNI_OK) return ergo_result;
if (PauseAtStartup) {
os::pause();
}
#ifndef USDT2
HS_DTRACE_PROBE(hotspot, vm__init__begin);
#else /* USDT2 */
HOTSPOT_VM_INIT_BEGIN();
#endif /* USDT2 */
// Record VM creation timing statistics
TraceVmCreationTime create_vm_timer;
create_vm_timer.start();
// Timing (must come after argument parsing)
TraceTime timer("Create VM", TraceStartupTime);
// Initialize the os module after parsing the args
jint os_init_2_result = os::init_2();
if (os_init_2_result != JNI_OK) return os_init_2_result;
jint adjust_after_os_result = Arguments::adjust_after_os();
if (adjust_after_os_result != JNI_OK) return adjust_after_os_result;
// intialize TLS
ThreadLocalStorage::init();
// Bootstrap native memory tracking, so it can start recording memory
// activities before worker thread is started. This is the first phase
// of bootstrapping, VM is currently running in single-thread mode.
MemTracker::bootstrap_single_thread();
// Initialize output stream logging
ostream_init_log();
// Convert -Xrun to -agentlib: if there is no JVM_OnLoad
// Must be before create_vm_init_agents()
if (Arguments::init_libraries_at_startup()) {
convert_vm_init_libraries_to_agents();
}
// Launch -agentlib/-agentpath and converted -Xrun agents
if (Arguments::init_agents_at_startup()) {
create_vm_init_agents();
}
// Initialize Threads state
_thread_list = NULL;
_number_of_threads = 0;
_number_of_non_daemon_threads = 0;
// Initialize global data structures and create system classes in heap
vm_init_globals();
// Attach the main thread to this os thread
JavaThread* main_thread = new JavaThread();
main_thread->set_thread_state(_thread_in_vm);
// must do this before set_active_handles and initialize_thread_local_storage
// Note: on solaris initialize_thread_local_storage() will (indirectly)
// change the stack size recorded here to one based on the java thread
// stacksize. This adjusted size is what is used to figure the placement
// of the guard pages.
main_thread->record_stack_base_and_size();
main_thread->initialize_thread_local_storage();
main_thread->set_active_handles(JNIHandleBlock::allocate_block());
if (!main_thread->set_as_starting_thread()) {
vm_shutdown_during_initialization(
"Failed necessary internal allocation. Out of swap space");
delete main_thread;
*canTryAgain = false; // don't let caller call JNI_CreateJavaVM again
return JNI_ENOMEM;
}
// Enable guard page *after* os::create_main_thread(), otherwise it would
// crash Linux VM, see notes in os_linux.cpp.
main_thread->create_stack_guard_pages();
// Initialize Java-Level synchronization subsystem
ObjectMonitor::Initialize() ;
// Second phase of bootstrapping, VM is about entering multi-thread mode
MemTracker::bootstrap_multi_thread();
// Initialize global modules
jint status = init_globals();
if (status != JNI_OK) {
delete main_thread;
*canTryAgain = false; // don't let caller call JNI_CreateJavaVM again
return status;
}
// Should be done after the heap is fully created
main_thread->cache_global_variables();
HandleMark hm;
{ MutexLocker mu(Threads_lock);
Threads::add(main_thread);
}
// Any JVMTI raw monitors entered in onload will transition into
// real raw monitor. VM is setup enough here for raw monitor enter.
JvmtiExport::transition_pending_onload_raw_monitors();
// Fully start NMT
MemTracker::start();
// Create the VMThread
{ TraceTime timer("Start VMThread", TraceStartupTime);
VMThread::create();
Thread* vmthread = VMThread::vm_thread();
if (!os::create_thread(vmthread, os::vm_thread))
vm_exit_during_initialization("Cannot create VM thread. Out of system resources.");
// Wait for the VM thread to become ready, and VMThread::run to initialize
// Monitors can have spurious returns, must always check another state flag
{
MutexLocker ml(Notify_lock);
os::start_thread(vmthread);
while (vmthread->active_handles() == NULL) {
Notify_lock->wait();
}
}
}
assert (Universe::is_fully_initialized(), "not initialized");
if (VerifyDuringStartup) {
// Make sure we're starting with a clean slate.
VM_Verify verify_op;
VMThread::execute(&verify_op);
}
EXCEPTION_MARK;
// At this point, the Universe is initialized, but we have not executed
// any byte code. Now is a good time (the only time) to dump out the
// internal state of the JVM for sharing.
if (DumpSharedSpaces) {
MetaspaceShared::preload_and_dump(CHECK_0);
ShouldNotReachHere();
}
// Always call even when there are not JVMTI environments yet, since environments
// may be attached late and JVMTI must track phases of VM execution
JvmtiExport::enter_start_phase();
// Notify JVMTI agents that VM has started (JNI is up) - nop if no agents.
JvmtiExport::post_vm_start();
{
TraceTime timer("Initialize java.lang classes", TraceStartupTime);
if (EagerXrunInit && Arguments::init_libraries_at_startup()) {
create_vm_init_libraries();
}
initialize_class(vmSymbols::java_lang_String(), CHECK_0);
// Initialize java_lang.System (needed before creating the thread)
initialize_class(vmSymbols::java_lang_System(), CHECK_0);
initialize_class(vmSymbols::java_lang_ThreadGroup(), CHECK_0);
Handle thread_group = create_initial_thread_group(CHECK_0);
Universe::set_main_thread_group(thread_group());
initialize_class(vmSymbols::java_lang_Thread(), CHECK_0);
oop thread_object = create_initial_thread(thread_group, main_thread, CHECK_0);
main_thread->set_threadObj(thread_object);
// Set thread status to running since main thread has
// been started and running.
java_lang_Thread::set_thread_status(thread_object,
java_lang_Thread::RUNNABLE);
// The VM creates & returns objects of this class. Make sure it's initialized.
initialize_class(vmSymbols::java_lang_Class(), CHECK_0);
// The VM preresolves methods to these classes. Make sure that they get initialized
initialize_class(vmSymbols::java_lang_reflect_Method(), CHECK_0);
initialize_class(vmSymbols::java_lang_ref_Finalizer(), CHECK_0);
call_initializeSystemClass(CHECK_0);
// get the Java runtime name after java.lang.System is initialized
JDK_Version::set_runtime_name(get_java_runtime_name(THREAD));
JDK_Version::set_runtime_version(get_java_runtime_version(THREAD));
// an instance of OutOfMemory exception has been allocated earlier
initialize_class(vmSymbols::java_lang_OutOfMemoryError(), CHECK_0);
initialize_class(vmSymbols::java_lang_NullPointerException(), CHECK_0);
initialize_class(vmSymbols::java_lang_ClassCastException(), CHECK_0);
initialize_class(vmSymbols::java_lang_ArrayStoreException(), CHECK_0);
initialize_class(vmSymbols::java_lang_ArithmeticException(), CHECK_0);
initialize_class(vmSymbols::java_lang_StackOverflowError(), CHECK_0);
initialize_class(vmSymbols::java_lang_IllegalMonitorStateException(), CHECK_0);
initialize_class(vmSymbols::java_lang_IllegalArgumentException(), CHECK_0);
}
// See : bugid 4211085.
// Background : the static initializer of java.lang.Compiler tries to read
// property"java.compiler" and read & write property "java.vm.info".
// When a security manager is installed through the command line
// option "-Djava.security.manager", the above properties are not
// readable and the static initializer for java.lang.Compiler fails
// resulting in a NoClassDefFoundError. This can happen in any
// user code which calls methods in java.lang.Compiler.
// Hack : the hack is to pre-load and initialize this class, so that only
// system domains are on the stack when the properties are read.
// Currently even the AWT code has calls to methods in java.lang.Compiler.
// On the classic VM, java.lang.Compiler is loaded very early to load the JIT.
// Future Fix : the best fix is to grant everyone permissions to read "java.compiler" and
// read and write"java.vm.info" in the default policy file. See bugid 4211383
// Once that is done, we should remove this hack.
initialize_class(vmSymbols::java_lang_Compiler(), CHECK_0);
// More hackery - the static initializer of java.lang.Compiler adds the string "nojit" to
// the java.vm.info property if no jit gets loaded through java.lang.Compiler (the hotspot
// compiler does not get loaded through java.lang.Compiler). "java -version" with the
// hotspot vm says "nojit" all the time which is confusing. So, we reset it here.
// This should also be taken out as soon as 4211383 gets fixed.
reset_vm_info_property(CHECK_0);
quicken_jni_functions();
// Must be run after init_ft which initializes ft_enabled
if (TRACE_INITIALIZE() != JNI_OK) {
vm_exit_during_initialization("Failed to initialize tracing backend");
}
// Set flag that basic initialization has completed. Used by exceptions and various
// debug stuff, that does not work until all basic classes have been initialized.
set_init_completed();
#ifndef USDT2
HS_DTRACE_PROBE(hotspot, vm__init__end);
#else /* USDT2 */
HOTSPOT_VM_INIT_END();
#endif /* USDT2 */
// record VM initialization completion time
#if INCLUDE_MANAGEMENT
Management::record_vm_init_completed();
#endif // INCLUDE_MANAGEMENT
// Compute system loader. Note that this has to occur after set_init_completed, since
// valid exceptions may be thrown in the process.
// Note that we do not use CHECK_0 here since we are inside an EXCEPTION_MARK and
// set_init_completed has just been called, causing exceptions not to be shortcut
// anymore. We call vm_exit_during_initialization directly instead.
SystemDictionary::compute_java_system_loader(THREAD);
if (HAS_PENDING_EXCEPTION) {
vm_exit_during_initialization(Handle(THREAD, PENDING_EXCEPTION));
}
#if INCLUDE_ALL_GCS
// Support for ConcurrentMarkSweep. This should be cleaned up
// and better encapsulated. The ugly nested if test would go away
// once things are properly refactored. XXX YSR
if (UseConcMarkSweepGC || UseG1GC) {
if (UseConcMarkSweepGC) {
ConcurrentMarkSweepThread::makeSurrogateLockerThread(THREAD);
} else {
ConcurrentMarkThread::makeSurrogateLockerThread(THREAD);
}
if (HAS_PENDING_EXCEPTION) {
vm_exit_during_initialization(Handle(THREAD, PENDING_EXCEPTION));
}
}
#endif // INCLUDE_ALL_GCS
// Always call even when there are not JVMTI environments yet, since environments
// may be attached late and JVMTI must track phases of VM execution
JvmtiExport::enter_live_phase();
// Signal Dispatcher needs to be started before VMInit event is posted
os::signal_init();
// Start Attach Listener if +StartAttachListener or it can't be started lazily
if (!DisableAttachMechanism) {
AttachListener::vm_start();
if (StartAttachListener || AttachListener::init_at_startup()) {
AttachListener::init();
}
}
// Launch -Xrun agents
// Must be done in the JVMTI live phase so that for backward compatibility the JDWP
// back-end can launch with -Xdebug -Xrunjdwp.
if (!EagerXrunInit && Arguments::init_libraries_at_startup()) {
create_vm_init_libraries();
}
// Notify JVMTI agents that VM initialization is complete - nop if no agents.
JvmtiExport::post_vm_initialized();
if (TRACE_START() != JNI_OK) {
vm_exit_during_initialization("Failed to start tracing backend.");
}
if (CleanChunkPoolAsync) {
Chunk::start_chunk_pool_cleaner_task();
}
// initialize compiler(s)
#if defined(COMPILER1) || defined(COMPILER2) || defined(SHARK)
CompileBroker::compilation_init();
#endif
if (EnableInvokeDynamic) {
// Pre-initialize some JSR292 core classes to avoid deadlock during class loading.
// It is done after compilers are initialized, because otherwise compilations of
// signature polymorphic MH intrinsics can be missed
// (see SystemDictionary::find_method_handle_intrinsic).
initialize_class(vmSymbols::java_lang_invoke_MethodHandle(), CHECK_0);
initialize_class(vmSymbols::java_lang_invoke_MemberName(), CHECK_0);
initialize_class(vmSymbols::java_lang_invoke_MethodHandleNatives(), CHECK_0);
}
#if INCLUDE_MANAGEMENT
Management::initialize(THREAD);
#endif // INCLUDE_MANAGEMENT
if (HAS_PENDING_EXCEPTION) {
// management agent fails to start possibly due to
// configuration problem and is responsible for printing
// stack trace if appropriate. Simply exit VM.
vm_exit(1);
}
if (Arguments::has_profile()) FlatProfiler::engage(main_thread, true);
if (MemProfiling) MemProfiler::engage();
StatSampler::engage();
if (CheckJNICalls) JniPeriodicChecker::engage();
BiasedLocking::init();
if (JDK_Version::current().post_vm_init_hook_enabled()) {
call_postVMInitHook(THREAD);
// The Java side of PostVMInitHook.run must deal with all
// exceptions and provide means of diagnosis.
if (HAS_PENDING_EXCEPTION) {
CLEAR_PENDING_EXCEPTION;
}
}
{
MutexLockerEx ml(PeriodicTask_lock, Mutex::_no_safepoint_check_flag);
// Make sure the watcher thread can be started by WatcherThread::start()
// or by dynamic enrollment.
WatcherThread::make_startable();
// Start up the WatcherThread if there are any periodic tasks
// NOTE: All PeriodicTasks should be registered by now. If they
// aren't, late joiners might appear to start slowly (we might
// take a while to process their first tick).
if (PeriodicTask::num_tasks() > 0) {
WatcherThread::start();
}
}
// Give os specific code one last chance to start
os::init_3();
create_vm_timer.end();
#ifdef ASSERT
_vm_complete = true;
#endif
return JNI_OK;
}
以上,就是vm创建的框架代码,也已经这么复杂了。大体有这么几个步骤:
1. 检查jdk版本号, 不支持则退出;
2. 输出流初始化;
3. sun.java.launcher属性配置检查接入;
4. 初始化一些系统模块,如随机数...;
5. 初始化系统属性如java.ext.dirs...;
6. 参数解析;
7. 系统页初始化;
8. 再初始化更多平台相关的系统模块, 如线程,页设置,mmap,PV机制,maxfd,优先级等;
9. ThreadLocalStorage初始化;
10. 内存跟踪器初始化;
11. agentlib 初始化;
12. 全局变量初始化;
13. 创建JavaThread, 初始化信息;
14. 将java线程映射到系统线程JavaThread -> OSThread;
15. ObjectMonitor对象监视器创建初始化;
16. 进入多线程模式;
17. 初始化java的全局模块,如bytecode,classloader...;
18. 添加main_thread到线程表中;
19. 创建 VMThread 线程, 启动vmThread线程并等待其事务处理完成;
20. JvmtiExport开始执行, 保证agent开始切入;
21. 初始化java系统类库,如system,string...;
22. 初始化编译器compiler;
23. jni函数信息设置;
24. jdwp调试模块运行;
25. AttachListener启动运行;
26. BiasedLocking 初始化;
27. WatcherThread 启动;
28. PeriodicTask 任务运行;
29. 执行完成, 返回创建成功;
细节就不说了(哈哈,因为说也说不清楚)。我们只需理解流程即可,真正想理解,那么就需要指定一个特定的小点,来进行探讨了。以后再说咯!
3. 核心变量JNIEnv 的前世今生
在jni.h中,JNINativeInterface_ 定义了许多的操作函数接口,即很多java的调用,都会调用这些方法。而这里面都有一个统一的第一个参数:JNIEnv* env 。可见其重要性。那么,这个变量又是如何初始化和创建的呢?既然是函数接口,那么必然需要具体的实现,这是具体在哪里定义的呢?
实际上,我们可以通过前面对 JNIEnv **penv 的赋值中查到端倪:
// hotspot/src/share/vm/prims/jni.cpp
...
// 将jvm信息存储到 penv 中,以备外部使用
*(JNIEnv**)penv = thread->jni_environment();
...
// 而查看 jni_environment() 方法可知,其由一个类变量 _jni_environment 处理
// share/vm/runtime/thread.hpp
// Returns the jni environment for this thread
JNIEnv* jni_environment() { return &_jni_environment; }
所以,我们只需找出 _jni_environment 是如何赋值初始化,即可知道如何获取这个关键变量的逻辑了。结果是,在创建JavaThread, 在进行初始化时,便会设置该值。
// share/vm/runtime/thread.cpp
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
Thread()
, _satb_mark_queue(&_satb_mark_queue_set),
_dirty_card_queue(&_dirty_card_queue_set)
{
if (TraceThreadEvents) {
tty->print_cr("creating thread %p", this);
}
// 初始化线程变量信息, 如 JNIEnv
initialize();
_jni_attach_state = _not_attaching_via_jni;
set_entry_point(entry_point);
// Create the native thread itself.
// %note runtime_23
os::ThreadType thr_type = os::java_thread;
thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
os::java_thread;
os::create_thread(this, thr_type, stack_sz);
_safepoint_visible = false;
// The _osthread may be NULL here because we ran out of memory (too many threads active).
// We need to throw and OutOfMemoryError - however we cannot do this here because the caller
// may hold a lock and all locks must be unlocked before throwing the exception (throwing
// the exception consists of creating the exception object & initializing it, initialization
// will leave the VM via a JavaCall and then all locks must be unlocked).
//
// The thread is still suspended when we reach here. Thread must be explicit started
// by creator! Furthermore, the thread must also explicitly be added to the Threads list
// by calling Threads:add. The reason why this is not done here, is because the thread
// object must be fully initialized (take a look at JVM_Start)
}
// A JavaThread is a normal Java thread
void JavaThread::initialize() {
// Initialize fields
// Set the claimed par_id to -1 (ie not claiming any par_ids)
set_claimed_par_id(-1);
set_saved_exception_pc(NULL);
set_threadObj(NULL);
_anchor.clear();
set_entry_point(NULL);
// 取数jni_functions, 初始化到 _jni_environment
set_jni_functions(jni_functions());
set_callee_target(NULL);
set_vm_result(NULL);
set_vm_result_2(NULL);
set_vframe_array_head(NULL);
set_vframe_array_last(NULL);
set_deferred_locals(NULL);
set_deopt_mark(NULL);
set_deopt_nmethod(NULL);
clear_must_deopt_id();
set_monitor_chunks(NULL);
set_next(NULL);
set_thread_state(_thread_new);
set_recorder(NULL);
_terminated = _not_terminated;
_privileged_stack_top = NULL;
_array_for_gc = NULL;
_suspend_equivalent = false;
_in_deopt_handler = 0;
_doing_unsafe_access = false;
_stack_guard_state = stack_guard_unused;
(void)const_cast
(_exception_oop = NULL); _exception_pc = 0;
_exception_handler_pc = 0;
_is_method_handle_return = 0;
_jvmti_thread_state= NULL;
_should_post_on_exceptions_flag = JNI_FALSE;
_jvmti_get_loaded_classes_closure = NULL;
_interp_only_mode = 0;
_special_runtime_exit_condition = _no_async_condition;
_pending_async_exception = NULL;
_thread_stat = NULL;
_thread_stat = new ThreadStatistics();
_blocked_on_compilation = false;
_jni_active_critical = 0;
_do_not_unlock_if_synchronized = false;
_cached_monitor_info = NULL;
_parker = Parker::Allocate(this) ;
_jmp_ring_index = 0;
for (int ji = 0 ; ji < jump_ring_buffer_size ; ji++ ) {
record_jump(NULL, NULL, NULL, 0);
}
set_thread_profiler(NULL);
if (FlatProfiler::is_active()) {
// This is where we would decide to either give each thread it's own profiler
// or use one global one from FlatProfiler,
// or up to some count of the number of profiled threads, etc.
ThreadProfiler* pp = new ThreadProfiler();
pp->engage();
set_thread_profiler(pp);
}
// Setup safepoint state info for this thread
ThreadSafepointState::create(this);
debug_only(_java_call_counter = 0);
// JVMTI PopFrame support
_popframe_condition = popframe_inactive;
_popframe_preserved_args = NULL;
_popframe_preserved_args_size = 0;
pd_initialize();
}
// Returns the function structure
struct JNINativeInterface_* jni_functions() {
if (CheckJNICalls) return jni_functions_check();
return &jni_NativeInterface;
}
// thread.hpp
//JNI functiontable getter/setter for JVMTI jni function table interception API.
void set_jni_functions(struct JNINativeInterface_* functionTable) {
_jni_environment.functions = functionTable;
}
所以,核心的初始化变成了 jni_NativeInterface 的具体值问题了。不过幸好,这是一个被定义为全局变量的,不至于被迷惑了。一看便知具体有哪些实现了。
// jni.cpp
// Structure containing all jni functions
struct JNINativeInterface_ jni_NativeInterface = {
NULL,
NULL,
NULL,
NULL,
jni_GetVersion,
jni_DefineClass,
jni_FindClass,
jni_FromReflectedMethod,
jni_FromReflectedField,
jni_ToReflectedMethod,
jni_GetSuperclass,
jni_IsAssignableFrom,
jni_ToReflectedField,
jni_Throw,
jni_ThrowNew,
jni_ExceptionOccurred,
jni_ExceptionDescribe,
jni_ExceptionClear,
jni_FatalError,
jni_PushLocalFrame,
jni_PopLocalFrame,
jni_NewGlobalRef,
jni_DeleteGlobalRef,
jni_DeleteLocalRef,
jni_IsSameObject,
jni_NewLocalRef,
jni_EnsureLocalCapacity,
jni_AllocObject,
jni_NewObject,
jni_NewObjectV,
jni_NewObjectA,
jni_GetObjectClass,
jni_IsInstanceOf,
jni_GetMethodID,
jni_CallObjectMethod,
jni_CallObjectMethodV,
jni_CallObjectMethodA,
jni_CallBooleanMethod,
jni_CallBooleanMethodV,
jni_CallBooleanMethodA,
jni_CallByteMethod,
jni_CallByteMethodV,
jni_CallByteMethodA,
jni_CallCharMethod,
jni_CallCharMethodV,
jni_CallCharMethodA,
jni_CallShortMethod,
jni_CallShortMethodV,
jni_CallShortMethodA,
jni_CallIntMethod,
jni_CallIntMethodV,
jni_CallIntMethodA,
jni_CallLongMethod,
jni_CallLongMethodV,
jni_CallLongMethodA,
jni_CallFloatMethod,
jni_CallFloatMethodV,
jni_CallFloatMethodA,
jni_CallDoubleMethod,
jni_CallDoubleMethodV,
jni_CallDoubleMethodA,
jni_CallVoidMethod,
jni_CallVoidMethodV,
jni_CallVoidMethodA,
jni_CallNonvirtualObjectMethod,
jni_CallNonvirtualObjectMethodV,
jni_CallNonvirtualObjectMethodA,
jni_CallNonvirtualBooleanMethod,
jni_CallNonvirtualBooleanMethodV,
jni_CallNonvirtualBooleanMethodA,
jni_CallNonvirtualByteMethod,
jni_CallNonvirtualByteMethodV,
jni_CallNonvirtualByteMethodA,
jni_CallNonvirtualCharMethod,
jni_CallNonvirtualCharMethodV,
jni_CallNonvirtualCharMethodA,
jni_CallNonvirtualShortMethod,
jni_CallNonvirtualShortMethodV,
jni_CallNonvirtualShortMethodA,
jni_CallNonvirtualIntMethod,
jni_CallNonvirtualIntMethodV,
jni_CallNonvirtualIntMethodA,
jni_CallNonvirtualLongMethod,
jni_CallNonvirtualLongMethodV,
jni_CallNonvirtualLongMethodA,
jni_CallNonvirtualFloatMethod,
jni_CallNonvirtualFloatMethodV,
jni_CallNonvirtualFloatMethodA,
jni_CallNonvirtualDoubleMethod,
jni_CallNonvirtualDoubleMethodV,
jni_CallNonvirtualDoubleMethodA,
jni_CallNonvirtualVoidMethod,
jni_CallNonvirtualVoidMethodV,
jni_CallNonvirtualVoidMethodA,
jni_GetFieldID,
jni_GetObjectField,
jni_GetBooleanField,
jni_GetByteField,
jni_GetCharField,
jni_GetShortField,
jni_GetIntField,
jni_GetLongField,
jni_GetFloatField,
jni_GetDoubleField,
jni_SetObjectField,
jni_SetBooleanField,
jni_SetByteField,
jni_SetCharField,
jni_SetShortField,
jni_SetIntField,
jni_SetLongField,
jni_SetFloatField,
jni_SetDoubleField,
jni_GetStaticMethodID,
jni_CallStaticObjectMethod,
jni_CallStaticObjectMethodV,
jni_CallStaticObjectMethodA,
jni_CallStaticBooleanMethod,
jni_CallStaticBooleanMethodV,
jni_CallStaticBooleanMethodA,
jni_CallStaticByteMethod,
jni_CallStaticByteMethodV,
jni_CallStaticByteMethodA,
jni_CallStaticCharMethod,
jni_CallStaticCharMethodV,
jni_CallStaticCharMethodA,
jni_CallStaticShortMethod,
jni_CallStaticShortMethodV,
jni_CallStaticShortMethodA,
jni_CallStaticIntMethod,
jni_CallStaticIntMethodV,
jni_CallStaticIntMethodA,
jni_CallStaticLongMethod,
jni_CallStaticLongMethodV,
jni_CallStaticLongMethodA,
jni_CallStaticFloatMethod,
jni_CallStaticFloatMethodV,
jni_CallStaticFloatMethodA,
jni_CallStaticDoubleMethod,
jni_CallStaticDoubleMethodV,
jni_CallStaticDoubleMethodA,
jni_CallStaticVoidMethod,
jni_CallStaticVoidMethodV,
jni_CallStaticVoidMethodA,
jni_GetStaticFieldID,
jni_GetStaticObjectField,
jni_GetStaticBooleanField,
jni_GetStaticByteField,
jni_GetStaticCharField,
jni_GetStaticShortField,
jni_GetStaticIntField,
jni_GetStaticLongField,
jni_GetStaticFloatField,
jni_GetStaticDoubleField,
jni_SetStaticObjectField,
jni_SetStaticBooleanField,
jni_SetStaticByteField,
jni_SetStaticCharField,
jni_SetStaticShortField,
jni_SetStaticIntField,
jni_SetStaticLongField,
jni_SetStaticFloatField,
jni_SetStaticDoubleField,
jni_NewString,
jni_GetStringLength,
jni_GetStringChars,
jni_ReleaseStringChars,
jni_NewStringUTF,
jni_GetStringUTFLength,
jni_GetStringUTFChars,
jni_ReleaseStringUTFChars,
jni_GetArrayLength,
jni_NewObjectArray,
jni_GetObjectArrayElement,
jni_SetObjectArrayElement,
jni_NewBooleanArray,
jni_NewByteArray,
jni_NewCharArray,
jni_NewShortArray,
jni_NewIntArray,
jni_NewLongArray,
jni_NewFloatArray,
jni_NewDoubleArray,
jni_GetBooleanArrayElements,
jni_GetByteArrayElements,
jni_GetCharArrayElements,
jni_GetShortArrayElements,
jni_GetIntArrayElements,
jni_GetLongArrayElements,
jni_GetFloatArrayElements,
jni_GetDoubleArrayElements,
jni_ReleaseBooleanArrayElements,
jni_ReleaseByteArrayElements,
jni_ReleaseCharArrayElements,
jni_ReleaseShortArrayElements,
jni_ReleaseIntArrayElements,
jni_ReleaseLongArrayElements,
jni_ReleaseFloatArrayElements,
jni_ReleaseDoubleArrayElements,
jni_GetBooleanArrayRegion,
jni_GetByteArrayRegion,
jni_GetCharArrayRegion,
jni_GetShortArrayRegion,
jni_GetIntArrayRegion,
jni_GetLongArrayRegion,
jni_GetFloatArrayRegion,
jni_GetDoubleArrayRegion,
jni_SetBooleanArrayRegion,
jni_SetByteArrayRegion,
jni_SetCharArrayRegion,
jni_SetShortArrayRegion,
jni_SetIntArrayRegion,
jni_SetLongArrayRegion,
jni_SetFloatArrayRegion,
jni_SetDoubleArrayRegion,
jni_RegisterNatives,
jni_UnregisterNatives,
jni_MonitorEnter,
jni_MonitorExit,
jni_GetJavaVM,
jni_GetStringRegion,
jni_GetStringUTFRegion,
jni_GetPrimitiveArrayCritical,
jni_ReleasePrimitiveArrayCritical,
jni_GetStringCritical,
jni_ReleaseStringCritical,
jni_NewWeakGlobalRef,
jni_DeleteWeakGlobalRef,
jni_ExceptionCheck,
jni_NewDirectByteBuffer,
jni_GetDirectBufferAddress,
jni_GetDirectBufferCapacity,
// New 1_6 features
jni_GetObjectRefType
};
以上就是 JNIEnv* env 变量的设值过程了,它借助于java线程的创建时机进行初始化。而后续的使用中,几乎都会仰仗它来运行,可见其重要性。至于具体的实现,且听后续分解。
腾讯、阿里、滴滴后台面试题汇总总结 — (含答案)
面试:史上最全多线程面试题 !
最新阿里内推Java后端面试题
JVM难学?那是因为你没认真看完这篇文章
关注作者微信公众号 —《JAVA烂猪皮》
了解更多java后端架构知识以及最新面试宝典
看完本文记得给作者点赞+在看哦~~~大家的支持,是作者源源不断出文的动力
作者:等你归去来
出处:https://www.cnblogs.com/yougewe/p/14406217.html