JVM是Java程序运行的环境,但是他同时一个操作系统的一个应用程序一个进程,因此他也有他自己的运行的生命周期,也有自己的代码和数据空间。
JVM工作原理和特点主要是指操作系统装入JVM是通过JDK中java.exe来完成,通过下面5步来完成JVM环境.
JVM提供的方式是操作系统的动态链接库文件(dll文件)
Java是通过GetApplicationHome
API来获得当前的Java.exe绝对路径:
该方法实现如下:
/*
* 如果一个应用服务的执行路径是:"c:\foo\bin\javac", 那么就会缓存"c:\foo"目录
*/
jboolean
GetApplicationHome(char *buf, jint bufsize)// buf 相当于 "c:\foo\bin\javac"
{
char *cp;
GetModuleFileName(0, buf, bufsize);
*JLI_StrRChr(buf, '\\') = '\0'; /* 移除路径中的.exe文件名*/
if ((cp = JLI_StrRChr(buf, '\\')) == 0) {
/* 当应用使用的是根目录而不包含bin\ 目录. */
buf[0] = '\0';
return JNI_FALSE;
}
*cp = '\0'; /* 移除目录中的 bin\ 部分 */
return JNI_TRUE;
}
再看看其调用者:
/*
* 根据当前任性的exe命令文件位置或者注册设置来找到JRE的根目录位置*/
jboolean
GetJREPath(char *path, jint pathsize){
char javadll[MAXPATHLEN];
struct stat s;
if (GetApplicationHome(path, pathsize)) {
/* Is JRE co-located with the application? */
JLI_Snprintf(javadll, sizeof(javadll), "%s\\bin\\" JAVA_DLL, path);
if (stat(javadll, &s) == 0) {
JLI_TraceLauncher("JRE path is %s\n", path);
return JNI_TRUE;
}
/* 确保存储路径 + \jre + NULL */
if ((JLI_StrLen(path) + 4 + 1) > pathsize) {
JLI_TraceLauncher("Insufficient space to store JRE path\n");
return JNI_FALSE;
}
/* 此应用程序是否在 应用程序家目录($JAVA_HOME)下有jre目录 */
JLI_Snprintf(javadll, sizeof (javadll), "%s\\jre\\bin\\" JAVA_DLL, path);
if (stat(javadll, &s) == 0) {
JLI_StrCat(path, "\\jre"); //如果有jre目录,那么将把$JAVA_HOME/jre作为jre根目录
JLI_TraceLauncher("JRE path is %s\n", path);
return JNI_TRUE;
}
}
显然,如果我的指令命令java.exe
所在路径是C:\jdk8\bin\java.exe
,那么那会去掉bin\java.exe
,然后进行各项判断,最终找到C:\jdk8\bin\jre
作为JRE的家目录。如果没有找到的话,最终会调用GetPublicJREHome
API 到你的注册表中HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\当前版本号\JAVA_HOME
去找的。
然后装载JVM.cfg文件:JRE路径\lib\ARCH(CPU构架)\JVM.cfg :
关于ARCH(CPU构架)的判断则是通过如下方法得到的:
/*
* Returns the arch path, to get the current arch use the
* macro GetArch, nbits here is ignored for now.
*/
const char *
GetArchPath(int nbits)
{
#ifdef _M_AMD64 // 貌似无论是AMDCPU还是现在普遍使用的英特尔处理器都是这个目录
return "amd64";
#elif defined(_M_IA64) // 英特尔安腾64位处理器
return "ia64";
#else
return "i386"; //32位处理器
#endif
}
例如我的电脑是Intel Core 8870U 64位的,那么目录是amd64
,也因此我的jvm.cfg
文件的路径是:
C:\jdk8\jre\lib\amd64\jvm.cfg
,其内容如下:
-server KNOWN # 默认第一选项即server类型 VM
-client IGNORE # IGNORE即忽略该模式,使用默认模式
值得注意的是,好像在后续的版本中,默认使用的都是server类型的vm了,甚至连之前默认的client版本的vm都不再支持了。
一般来说,默认的java.dll
文件存储位置是在KaTeX parse error: Expected 'EOF', got '\jdk' at position 27: …n/VM类型,例如我的:`C:\̲j̲d̲k̲8\jre\bin\serve…JRE_HOME/bin下面并没有类似client等字眼,即不支持client vm了。
要知道,官方之所以选择server vm,而放弃client vm主要还是因为client vm是轻量级的,启动时速度比较快,但是稳定后,其执行速度远不及server vm,而sever vm虽然是重量级的,但是其内部的优化做的比较好,所以一旦稳定下来,server vm可以吊打 client vm。
通过第一步已经找到了JVM的路径,Java通过LoadJavaVM来装入JVM.dll文件.装入工作很简单就是调用Windows API函数:
/*
* 从JRE路径中加载一个jvm,并初始化调用函数
*/
jboolean
LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
{
HINSTANCE handle;
JLI_TraceLauncher("JVM path is %s\n", jvmpath);
/*
* 首选需要加载C运行时库,副本是假定存在于“jre path”目录中。如果找不到这里(或者“jre路径”解析失 * 败),跳过显式加载并按照默认处理方式进行处理,这很可能是执行失败。
*/
LoadMSVCRT();
/* 加载jvm.dll动态链接库文件 */
if ((handle = LoadLibrary(jvmpath)) == 0) {
JLI_ReportErrorMessage(DLL_ERROR4, (char *)jvmpath);
return JNI_FALSE;
}
/* 现在获取这个函数的地址信息 */
ifn->CreateJavaVM =
(void *)GetProcAddress(handle, "JNI_CreateJavaVM");
ifn->GetDefaultJavaVMInitArgs =
(void *)GetProcAddress(handle, "JNI_GetDefaultJavaVMInitArgs");
if (ifn->CreateJavaVM == 0 || ifn->GetDefaultJavaVMInitArgs == 0) {
JLI_ReportErrorMessage(JNI_ERROR1, (char *)jvmpath);
return JNI_FALSE;
}
return JNI_TRUE;
}
LoadLibrary装载JVM.dll动态连接库.然后把JVM.dll中的导出函数JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs挂接到InvocationFunctions变量的CreateJavaVM和GetDefaultJavaVMInitArgs函数指针变量上。JVM.dll的装载工作宣告完成。
获得本地调用接口,这样就可以在Java中调用JVM的函数了.调用InvocationFunctions->CreateJavaVM也就是JVM中JNI_CreateJavaVM方法获得JNIEnv结构的实例.
Java程序有两种方式:
一种是jar包
一种是class
. 运行jar
Java -jar XXX.jar
运行的时候,Java.exe调用GetApplicationClass
函数,该函数先获得JNIEnv实例然后调用Java类Java.util.jar.JarFileJNIEnv中方法getManifest()并从返回的Manifest对象中取getAttributes(“Main-Class”)的值即jar包中文件:META-INF/MANIFEST.MF指定的Main-Class的主类名作为运行的主类。之后main函数会调用Java.c中LoadMainClass
方法装载该主类(使用JNIEnv实例的FindClass)。main函数直接调用Java.c中LoadMainClass
方法装载该类。
static jclass
GetApplicationClass(JNIEnv *env)
{
jmethodID mid;
jobject result;
jclass cls = GetLauncherHelperClass(env);
NULL_CHECK0(cls);
NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
"getApplicationClass",
"()Ljava/lang/Class;"));
return (*env)->CallStaticObjectMethod(env, cls, mid);
}
/*
* For tools, convert command line args thus:
* javac -cp foo:foo/"*" -J-ms32m ...
* java -ms32m -cp JLI_WildcardExpandClasspath(foo:foo/"*") ...
*
* Takes 4 parameters, and returns the populated arguments
*/
static void
TranslateApplicationArgs(int jargc, const char **jargv, int *pargc, char ***pargv)
{
int argc = *pargc;
char **argv = *pargv;
int nargc = argc + jargc;
char **nargv = JLI_MemAlloc((nargc + 1) * sizeof(char *));
int i;
*pargc = nargc;
*pargv = nargv;
/* Copy the VM arguments (i.e. prefixed with -J) */
for (i = 0; i < jargc; i++) {
const char *arg = jargv[i];
if (arg[0] == '-' && arg[1] == 'J') {
*nargv++ = ((arg + 2) == NULL) ? NULL : JLI_StringDup(arg + 2);
}
}
for (i = 0; i < argc; i++) {
char *arg = argv[i];
if (arg[0] == '-' && arg[1] == 'J') {
if (arg[2] == '\0') {
JLI_ReportErrorMessage(ARG_ERROR3);
exit(1);
}
*nargv++ = arg + 2;
}
}
/* Copy the rest of the arguments */
for (i = 0; i < jargc ; i++) {
const char *arg = jargv[i];
if (arg[0] != '-' || arg[1] != 'J') {
*nargv++ = (arg == NULL) ? NULL : JLI_StringDup(arg);
}
}
for (i = 0; i < argc; i++) {
char *arg = argv[i];
if (arg[0] == '-') {
if (arg[1] == 'J')
continue;
if (IsWildCardEnabled() && arg[1] == 'c'
&& (JLI_StrCmp(arg, "-cp") == 0 ||
JLI_StrCmp(arg, "-classpath") == 0)
&& i < argc - 1) {
*nargv++ = arg;
*nargv++ = (char *) JLI_WildcardExpandClasspath(argv[i+1]);
i++;
continue;
}
}
*nargv++ = arg;
}
*nargv = 0;
}
/*
* 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;
jclass cls = GetLauncherHelperClass(env);
NULL_CHECK0(cls);
if (JLI_IsTraceLauncher()) {
start = CounterGet();
}
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;
}
如果是执行class方法。main函数直接调用Java.c中LoadMainClass
方法装载该类。然后main函数调用JNIEnv实例的GetStaticMethodID方法查找装载的class主类中“public static void main(String[] args)”方法,并判断该方法是否为public方法,然后调用JNIEnv实例的CallStaticVoidMethod方法调用该Java类的main方法。