对于Java项目在运行的时候是如何工作的,这个问题我一直比较模糊,虽然知道是那三种类加载机制(bootstrapClassLoader,extendsionClassLoader和systemAppClassLoader),但具体是怎么实现的呢?
Java在加载JVM的时候会先加载jdk的一些环境变量,例如jre的路径、jvm的路径等,这些过程都是由C语言实现的。代码位于hotspotsrcshare oolslauncher下面java.c
main(int argc, char ** argv)
{
char *jarfile = 0;
char *classname = 0;
char *s = 0;
char *main_class = NULL;
int ret;
InvocationFunctions ifn;
jlong start, end;
char jrepath[MAXPATHLEN], jvmpath[MAXPATHLEN];
char ** original_argv = argv;
if (getenv("_JAVA_LAUNCHER_DEBUG") != 0) {
_launcher_debug = JNI_TRUE;
printf("----_JAVA_LAUNCHER_DEBUG----
");
}
{
int i;
original_argv = (char**)JLI_MemAlloc(sizeof(char*)*(argc+1));
for(i = 0; i < argc+1; i++)
original_argv[i] = argv[i];
}
CreateExecutionEnvironment(&argc, &argv,
jrepath, sizeof(jrepath),
jvmpath, sizeof(jvmpath),
original_argv);
printf("Using java runtime at: %s
", jrepath);
ifn.CreateJavaVM = 0;
ifn.GetDefaultJavaVMInitArgs = 0;
if (_launcher_debug)
start = CounterGet();
if (!LoadJavaVM(jvmpath, &ifn)) {
exit(6);
}
if (_launcher_debug) {
end = CounterGet();
printf("%ld micro seconds to LoadJavaVM
",
(long)(jint)Counter2Micros(end-start));
}
}
当加载完jvm后接下来会设置ClassPath,jvm的堆栈大小
/* * 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; } }
这些准备工作完成后,接下来就开始调用我们Java项目的main方法了(源码见java.c的JavaMain函数)
int JNICALL JavaMain(void * _args) { struct JavaMainArgs *args = (struct JavaMainArgs *)_args; int argc = args->argc; char **argv = args->argv; char *jarfile = args->jarfile; char *classname = args->classname; InvocationFunctions ifn = args->ifn; JavaVM *vm = 0; JNIEnv *env = 0; jstring mainClassName; jclass mainClass; jmethodID mainID; jobjectArray mainArgs; int ret = 0; jlong start, end; ... }
首先会初始化一大批参数,Java程序有两种方式一种是jar包,一种是class. 运行jar,Java -jar
XXX.jar运行的时候,Java.exe调用GetMainClassName函数,该函数先获得JNIEnv实例然后调用Java类Java.util.jar.JarFileJNIEnv中方法getManifest()并从返回的Manifest对象中取getAttributes("Main-Class")的值即jar包中文件:META-INF/MANIFEST.MF指定的Main-Class的主类名作为运行的主类。之后main函数会调用Java.c中LoadClass方法装载该主类(使用JNIEnv实例的FindClass)。
/* * 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). */ if (jarfile != 0) { mainClassName = GetMainClassName(env, jarfile); if ((*env)->ExceptionOccurred(env)) { ReportExceptionDescription(env); goto leave; } if (mainClassName == NULL) { const char * format = "Failed to load Main-Class manifest " "attribute from %s"; message = (char*)JLI_MemAlloc((strlen(format) + strlen(jarfile)) * sizeof(char)); sprintf(message, format, jarfile); messageDest = JNI_TRUE; goto leave; } classname = (char *)(*env)->GetStringUTFChars(env, mainClassName, 0); if (classname == NULL) { ReportExceptionDescription(env); goto leave; } mainClass = LoadClass(env, classname); if(mainClass == NULL) { /* exception occured */ const char * format = "Could not find the main class: %s. Program will exit."; ReportExceptionDescription(env); message = (char *)JLI_MemAlloc((strlen(format) + strlen(classname)) * sizeof(char) ); messageDest = JNI_TRUE; sprintf(message, format, classname); goto leave; } (*env)->ReleaseStringUTFChars(env, mainClassName, classname); } else { mainClassName = NewPlatformString(env, classname); if (mainClassName == NULL) { const char * format = "Failed to load Main Class: %s"; message = (char *)JLI_MemAlloc((strlen(format) + strlen(classname)) * sizeof(char) ); sprintf(message, format, classname); messageDest = JNI_TRUE; goto leave; } classname = (char *)(*env)->GetStringUTFChars(env, mainClassName, 0); if (classname == NULL) { ReportExceptionDescription(env); goto leave; } mainClass = LoadClass(env, classname); if(mainClass == NULL) { /* exception occured */ const char * format = "Could not find the main class: %s. Program will exit."; ReportExceptionDescription(env); message = (char *)JLI_MemAlloc((strlen(format) + strlen(classname)) * sizeof(char) ); messageDest = JNI_TRUE; sprintf(message, format, classname); goto leave; } (*env)->ReleaseStringUTFChars(env, mainClassName, classname); } /* Get the application's main method */ mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V"); if (mainID == NULL) { if ((*env)->ExceptionOccurred(env)) { ReportExceptionDescription(env); } else { message = "No main method found in specified class."; messageDest = JNI_TRUE; } goto leave; } { /* Make sure the main method is public */ jint mods; jmethodID mid; jobject obj = (*env)->ToReflectedMethod(env, mainClass, mainID, JNI_TRUE); if( obj == NULL) { /* exception occurred */ ReportExceptionDescription(env); goto leave; } mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, obj), "getModifiers", "()I"); if ((*env)->ExceptionOccurred(env)) { ReportExceptionDescription(env); goto leave; } mods = (*env)->CallIntMethod(env, obj, mid); if ((mods & 1) == 0) { /* if (!Modifier.isPublic(mods)) ... */ message = "Main method not public."; messageDest = JNI_TRUE; goto leave; } }
GetMainClassName()函数的实现过程如下:
static jstring GetMainClassName(JNIEnv *env, char *jarname) { #define MAIN_CLASS "Main-Class" jclass cls; jmethodID mid; jobject jar, man, attr; jstring str, result = 0; NULL_CHECK0(cls = (*env)->FindClass(env, "java/util/jar/JarFile")); NULL_CHECK0(mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V")); NULL_CHECK0(str = NewPlatformString(env, jarname)); NULL_CHECK0(jar = (*env)->NewObject(env, cls, mid, str)); NULL_CHECK0(mid = (*env)->GetMethodID(env, cls, "getManifest", "()Ljava/util/jar/Manifest;")); man = (*env)->CallObjectMethod(env, jar, mid); if (man != 0) { NULL_CHECK0(mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, man), "getMainAttributes", "()Ljava/util/jar/Attributes;")); attr = (*env)->CallObjectMethod(env, man, mid); if (attr != 0) { NULL_CHECK0(mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, attr), "getValue", "(Ljava/lang/String;)Ljava/lang/String;")); NULL_CHECK0(str = NewPlatformString(env, MAIN_CLASS)); result = (*env)->CallObjectMethod(env, attr, mid, str); } } return result; }
如果是执行class方法。则先调用NewPlatformString(env, classname)获取mainClassName,然后直接调用LoadClass(env, classname)来加载该类。NewPlatformString()函数如下:
/* * Returns a new Java string object for the specified platform string. */ static jstring NewPlatformString(JNIEnv *env, char *s) { int len = (int)strlen(s); jclass cls; jmethodID mid; jbyteArray ary; jstring enc; if (s == NULL) return 0; enc = getPlatformEncoding(env); ary = (*env)->NewByteArray(env, len); if (ary != 0) { jstring str = 0; (*env)->SetByteArrayRegion(env, ary, 0, len, (jbyte *)s); if (!(*env)->ExceptionOccurred(env)) { if (isEncodingSupported(env, enc) == JNI_TRUE) { NULL_CHECK0(cls = (*env)->FindClass(env, "java/lang/String")); NULL_CHECK0(mid = (*env)->GetMethodID(env, cls, "<init>", "([BLjava/lang/String;)V")); str = (*env)->NewObject(env, cls, mid, ary, enc); } else { /*If the encoding specified in sun.jnu.encoding is not endorsed by "Charset.isSupported" we have to fall back to use String(byte[]) explicitly here without specifying the encoding name, in which the StringCoding class will pickup the iso-8859-1 as the fallback converter for us. */ NULL_CHECK0(cls = (*env)->FindClass(env, "java/lang/String")); NULL_CHECK0(mid = (*env)->GetMethodID(env, cls, "<init>", "([B)V")); str = (*env)->NewObject(env, cls, mid, ary); } (*env)->DeleteLocalRef(env, ary); return str; } } return 0; }
至于加载类方法,直接将类名的指针压入JVM的堆栈中,过程如下:
/* * Loads a class, convert the '.' to '/'. */ static jclass LoadClass(JNIEnv *env, char *name) { char *buf = JLI_MemAlloc(strlen(name) + 1); char *s = buf, *t = name, c; jclass cls; jlong start, end; if (_launcher_debug) start = CounterGet(); do { c = *t++; *s++ = (c == '.') ? '/' : c; } while (c != '