openJDK源码分析

对于Java程序来说,JVM是一台完整的机器,可是对于真正的机器或OS来说,JVM只是其中一个进程而已。作为一个进程,它又是如何启动的呢?如下来一步一步分析。(注,本文分析的JDK源代码是openjdk6,下载地址:http://download.java.net/openjdk/jdk6/) 

    JVM的进程入口是在...\jdk\src\share\bin\java.c。199行。该方法一开始初始化了一系列的指针并判断是否打开debug(通过set _JAVA_LAUNCHER_DEBUG=1打开).接下来就进入了CreateExecutionEnvironment函数,该函数主要作用是查找Java程序的运行目录。那么这个函数在中间到底做了些什么呢? 两件事: 

    (1)、查找jre路径:这个功能是通过...\jdk\src\ 
\bin\java_md.c中的GetJREPath函数实现的。首先其通过GetApplicationHome获取到路径,代码如下: 

[code="c"]/* 
* If app is "c:\foo\bin\javac", then put "c:\foo" into buf. 
*/ 
jboolean 
GetApplicationHome(char *buf, jint bufsize) 

    char *cp; 
    GetModuleFileName(0, buf, bufsize); 
    *strrchr(buf, '\\') = '\0'; /* remove .exe file name */ 
    if ((cp = strrchr(buf, '\\')) == 0) { 
        /* This happens if the application is in a drive root, and 
         * there is no bin directory. */ 
        buf[0] = '\0'; 
        return JNI_FALSE; 
    } 
    *cp = '\0';  /* remove the bin\ part */ 
    retur JNI_TRUE; 




找到ApplicationHome之后,GetJREPath函数会通过如下的方式来确定JREPath。 
 

GetPublicJREHome函数的逻辑就是先查找 

HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\CurrentVersion 

键值“当前JRE版本号”,然后取HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\“当前JRE版本号”\JavaHome的路径所在 为JRE路径。 



小验证: 

打开DEBUG,执行java -ersion,得到: 

----_JAVA_LAUNCHER_DEBUG---- 

Version major.minor.micro = 1.6.0 

JRE path is E:\Java\jre1.6.0 

............................. 



执行javac,得到: 

----_JAVA_LAUNCHER_DEBUG---- 

JRE path is E:\Java\jdk1.6.0\jre 

.................. 

这里要思考的是:为什么2者不一样呢?? 



其实,逻辑还是上面的那一套逻辑,只是忽略的一点是,在我们安装JDK的时候,会拷贝一份java.exe到C:\WINDOWS\system32\下,系统的Path目录包含了该目录,且先于我们配置的JAVA目录,所以,执行java -ersion的时候使用的是Windows目录下的那个java.exe而不是JAVA目录下的,所以导致走的PublicJRE的那个路径。 



    (2)、获取当前环境需要装载的jvm.dll文件路径: 
        要获取本机平台上应该使用哪个jvm.dll,是先通过java.c的ReadKnownVMs方法获取当前主机的jvm.cfg.该函数逻辑就是构造“JRE路径+\lib+\ARCH(CPU构架)+\jvm.cfg”路径。(ARCH的值是通过java_md.c的GetArch实现的。) 

[code="c"]GetArch() 

#ifdef _M_AMD64 
    return "amd64"; 
#elif defined(_M_IA64) 
    return "ia64"; 
#else 
    return "i386"; 
#endif 




jvm.cfg文件的格式如下: 



# @(#)jvm.cfg 1.8 05/11/17 



# Copyright 2006 Sun Microsystems, Inc. All rights reserved. 

# SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 









# List of JVMs that can be used as an option to java, javac, etc. 

# Order is important -- first in this list is the default JVM. 

# NOTE that this both this file and its format are UNSUPPORTED and 

# WILL GO AWAY in a future release. 



# You may also select a JVM in an arbitrary location with the 

# "-XXaltjvm=" option, but that too is unsupported 

# and may not be available in a future release. 



-client KNOWN 

-server KNOWN 

-hotspot ALIASED_TO -client 

-classic WARN 

-native ERROR 

-green ERROR 



ReadKnownVMs函数会将该文件中的配置内容读入到一个JVM配置结构的全局变量中,该函数首先跳过注释(以‘#’开始的行),然后读取以‘-’开始的行指定的jvm参数,每一行为一个jvm信息,第一部分为jvm虚拟机名称,第二部分为配置参数,比如行:“-client KNOWN”则“-client”为虚拟机名称,而“KNOWN”为配置类型参数,“KNOWN”表示该虚拟机的jvm.dll存在,而“ALIASED_TO”表示为另一个jvm.dll的别名,“WARN”表示该虚拟机的jvm.dll不存在但运行时会用其他存在的jvm.dll替代执行,而“ERROR”同样表示该类虚拟机的jvm.dll不存在且运行时不会找存在的jvm.dll替代而直接抛出错误信息。 

    加载完jvm.cfg文件之后,java.c会调用CheckJvmType方法去检测本机应该使用哪一个类型的JVM。该方法会首先判断用户是否有指定JVM类型,如果有就从前加载的jvm.cfg的数据中查找是否有用户指定的类型。如果用户指定的类型不合法,会抛异常。如果用户没有指定JVM类型,则直接返回jvm.cfg的第一项。 

     用户可以通过4种方式指定jvm的类型,“按照jvm.cfg文件中的jvm名称指定”,“java -J”、“java -XXaltjvm=”和“java -J-XXaltjvm=”。 

     获取到JVM的类型之后,进程会通过java_md.c的GetJVMPath的方法,把前面获取的JREPath,JVMType一起组成jvm.dll的绝对路径“JRE路径+\bin+\jvm类型字符串+\jvm.dll”。 



  以上是我个人学习总结所得,可能会有很多理解偏颇之处,欢迎指正探讨,也让我不要在错误的道路上越走越远,谢谢,龙年吉祥。 



找到jvm.dll之后呢? ——加载。 

    也就是获取到应该加载的jvmPath之后,java.c接下来执行到263行: 
 
C代码   收藏代码
  1. if (!LoadJavaVM(jvmpath, &ifn)) {  
  2.     exit(6);  
  3.   }  
,通过java_md.c的LoadJavaVM函数来加载整个JVM虚拟机,该方法有2个参数: 第一个是我们之前获取得到的jvm.dll的绝对路径; 
     另一个是main函数一开始就初始化的一个结构体InvocationFunctions。 

   LoadJVM首先通过LoadLibrary函数加载了jvm.dll,然后把其中的2个方法JNI_CreateJavaVM 和 JNI_GetDefaultJavaVMInitArgs 挂载到参数ifn的结构体中。 
C代码   收藏代码
  1. /* Load the Java VM DLL */  
  2.     if ((handle = LoadLibrary(jvmpath)) == 0) {  
  3.         ReportErrorMessage2("Error loading: %s", (char *)jvmpath, JNI_TRUE);  
  4.         return JNI_FALSE;  
  5.     }  
  6.   
  7.     /* Now get the function addresses */  
  8.     ifn->CreateJavaVM =  
  9.         (void *)GetProcAddress(handle, "JNI_CreateJavaVM");  
  10.     ifn->GetDefaultJavaVMInitArgs =  
  11.         (void *)GetProcAddress(handle, "JNI_GetDefaultJavaVMInitArgs");  

    
    加载完jvm.dll,接下来main函数继续初始化接收到的所有参数,然后把所有初始化好的jvm参数和之前我们加载好的InvocationFunctions一起赋值给JavaMainArgs 
C代码   收藏代码
  1. struct JavaMainArgs {  
  2.   int     argc;  
  3.   char ** argv;  
  4.   char *  jarfile;  
  5.   char *  classname;  
  6.   InvocationFunctions ifn;  
  7. };  

    构造好JavaMainArgs 之后,会执行到java.c的370行。 
C代码   收藏代码
  1. ContinueInNewThread(JavaMain, threadStackSize, (void*)&args, ret);  
新起一个线程去执行JavaMain函数。 
    JavaMain函数经过一系列初始化之后,会调用java.c中的InitializeJVM函数来初始化一个JVM虚拟机,该函数主要执行逻辑就一句话,在java.c的1269行
Java代码   收藏代码
  1. r = ifn->CreateJavaVM(pvm, (void **)penv, &args);  
。 
    就是通过调用我们之前挂在的InvocationFunctions的CreateJVM创建一个虚拟机,而CreateJVM挂载的是jvm.dll中的JNI_CreateJavaVM方法。JNI_CreateJavaVM方法的源代码在\hotspot\src\share\vm\prims\jni.cpp中。该方法的定义如下: 
C代码   收藏代码
  1. _JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args)  
该方法有3个参数,都是从java.c的InitializeJVM函数传过来的:   第一个参数是javaVM指针的指针; 
       第二个参数是JNIEnv类型的指针的指针; 
       第三个参数是一个虚拟机的参数的指针。该函数主要的功能
C代码   收藏代码
  1. result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);  
  2.   if (result == JNI_OK) {  
  3.     JavaThread *thread = JavaThrad::current();  
  4.     /* thread is thread_in_vm heere */  
  5.     *vm = (JavaVM *)(&main_vm);  
  6.     *(JNIEnv**)penv = thread->jni_environment();  

    一是根据jvm启动参数构建一个VM虚拟机; 
    二是构建了一个JNIEnv的实例挂载到main中的JNIEnv结构的指针。这样,java.c的InitializeJVM函数就可以使用这个JNIEnv实例继续做下面的操作。 
    构造好了VM实例和JNIEnv,JavaMain函数开始加载Class,它首先会寻找java程序的入口类,即MainClass。
Java代码   收藏代码
  1. if (jarfile != 0) {  
  2.         mainClassName = GetMainClassName(env, jarfile);  
  3.         .....  
  4.     } else {  
  5.         mainClassName = NewPlatformString(env, classname);  
  6.       ....  
  7.     }  
如果执行的是jar文件,则会通过GetMainClassName函数查找jar中的MainClass.具体逻辑就是先实例化java/util/jar/JarFile类,然后获取执行的jar文件中的Manifest对象的"Main-Class"属性。——即jar包中的META-INF/MANIFEST.MF文件的"Main-Class"属性。 
       如果直接是执行的java类,就直接以该类为MainClass。然后调用GetStaticMethodID函数获MainClass中的public static void main(str[] args)方法,找到MainClass中的入口函数,就会初始化MainClass的参数
C代码   收藏代码
  1. mainArgs = NewPlatformStringArray(env, argv, argc);  
,然后调用前面获取到的JNIEnv实例调用CallStaticVoidMethod方法执行入口函数。
C代码   收藏代码
  1. /* Invoke main method. */  
  2.     (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);  
。 
这样,程序才执行到我们自己所写的代码。 

参考文章:http://zhidao.baidu.com/question/5956019 

         http://blog.163.com/irene_lwx@126/blog/static/28064560200792864055915/ 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值