JRE(Java运行时环境)和windows装载JVM(转载/整理)
上次简单说明了JDK,JRE和JVM的概念,这次说一下java程序从源码到可执行机器码的过程
先看一个简单的流程图(来自 http://baike.baidu.com/picview/160708/160708/0/ac75478285acf3b90cf4d213.html#albumindex=0&picindex=5)
其中从java源代码到java字节码的详细编译过程看下图(来自 http://baike.baidu.com/picview/160708/160708/0/ac75478285acf3b90cf4d213.html#albumindex=0&picindex=1)
其中java运行时环境中 JVM架构看下图(来自 http://baike.baidu.com/picview/160708/160708/0/ac75478285acf3b90cf4d213.html#albumindex=0&picindex=0)
根据上图,可简单的将JVM分为三部分:类装载子系统,用于装载.class文件(也要完成校验字节码的工作);执行引擎,执行字节码或本地方法(我想这应该是java解释器和JIT代码生成器进行的工作);运行时数据域,就是java堆栈中及寄存器中的内容。在运行时数据域中特别说明一下本地方法栈,当一个线程调用本地方法时,它就不再受到虚拟机关于结构和安全限制方面的约束,它既可以访问虚拟机的运行期数据区,也可以使用本地处理器以及任何类型的栈。例如,本地栈是一个C语言的栈,那么当C程序调用C函数时,函数的参数以某种顺序被压入栈,结果则返回给调用函数。在实现Java虚拟机时,本地方法接口使用的是C语言的模型栈,那么它的本地方法栈的调度与使用则完全与C语言的栈相同。
需要注意的是,JVM在执行字节码时,最终还需把字节码解释成具体平台上的机器指令执行。所以对应于不同的平台,JVM中的解释器和即时编译器(更多被称作即时代码生成器,JIN代码生成器)不同,亦即对应不同的平台有不同的JVM。
Windows装载JVM是通过JDK中的java.exe来完成的:
1、首先查找jre路径,java是通过GetApplicationHome这个API来获得当前的java.exe绝对路径,C:/Program Files/Java/jdk1.6.0/bin/java.exe,那么它会截取到绝对路径C:/Program Files/Java/jdk1.6.0/,判断C:/Program Files/Java/jdk1.6.0/bin/java.dll文件是否存在,如果存在就把C:/Program Files/Java/jdk1.6.0/作为jre路径,如果不存在则判断C:/Program Files/Java/jdk1.6.0/jre/bin/java.dll是否存在,如果存在这C:/Program Files/Java/jdk1.6.0/jre作为jre路径.如果不存在调用GetPublicJREHome查HKEY_LOCAL_MACHINE/Software/JavaSoft/Java Runtime Environment/“当前JRE版本号”/JavaHome的路径为jre路径。
然后装载jvm.cfg文件JRE路径+/lib+/ARCH(CPU构架)+/jvm.cfgARCH(CPU构架)的判断是通过java_md.c中GetArch函数判断的,该函数中windows平台只有两种情况:WIN64的‘ia64’,其他情况都为‘i386’。
以我的为例:C:/Program Files/Java/jdk1.6.0/jre/lib/i386/jvm.cfg.主要的内容如下:
-client KNOWN
-server KNOWN
-hotspot ALIASED_TO -client
-classic WARN
-native ERROR
-green ERROR
在我们的jdk目录中jre/bin/server和jre/bin/client都有jvm.dll文件存在,而java正是通过jvm.cfg配置文件来管理这些不同版本的jvm.dll的.通过文件我们可以定义目前jdk中支持那些jvm,前面部分(client)是jvm名称,后面是参数,KNOWN表示jvm存在,ALIASED_TO表示给别的jvm取一个别名,WARN表示不存在时找一个jvm替代,ERROR表示不存在抛出异常.在运行java XXX是,java.exe会通过CheckJvmType来检查当前的jvm类型,java可以通过两种参数的方式来指定具体的jvm类型,一种按照jvm.cfg文件中的jvm名称指定,第二种方法是直接指定,它们执行的方法分别是“java -J”、“java -XXaltjvm=”或“java -J-XXaltjvm=”。如果是第一种参数传递方式,CheckJvmType函数会取参数‘-J’后面的jvm名称,然后从已知的jvm配置参数中查找如果找到同名的则去掉该jvm名称前的‘-’直接返回该值;而第二种方法,会直接返回“-XXaltjvm=”或“-J-XXaltjvm=”后面的jvm类型名称;如果在运行java时未指定上面两种方法中的任一一种参数,CheckJvmType会取配置文件中第一个配置中的jvm名称,去掉名称前面的‘-’返回该值。CheckJvmType函数的这个返回值会在下面的函数中汇同jre路径组合成jvm.dll的绝对路径。如果没有指定这会使用jvm.cfg中第一个定义的jvm.可以通过set _JAVA_LAUNCHER_DEBUG=1在控制台上测试.
最后获得jvm.dll的路径,JRE路径+/bin+/jvm类型字符串+/jvm.dll就是jvm的文件路径了,但是如果在调用java程序时用-XXaltjvm=参数指定的路径path,就直接用path+/jvm.dll文件做为jvm.dll的文件路径.
2、装载jvm.dll
通过第一步已经找到了jvm的路径,java通过LoadJavaVM来装入jvm.dll文件.装入工作很简单就是调用windows API函数:LoadLibrary装载jvm.dll动态连接库.然后把jvm.dll中的导出函数JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs挂接到InvocationFunctions变量的CreateJavaVM和GetDefaultJavaVMInitArgs函数指针变量上。jvm.dll的装载工作宣告完成。
3、初始化jvm,获得本地调用接口,这样就可以在java中调用jvm的函数了.调用InvocationFunctions->CreateJavaVM也就是jvm中JNI_CreateJavaVM方法获得JNIEnv结构的实例.
4、运行java程序.
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)。main函数直接调用java.c中LoadClass方法装载该类。如果是执行class方法。main函数直接调用java.c中LoadClass方法装载该类。
然后main函数调用JNIEnv实例的GetStaticMethodID方法查找装载的class主类中“public static void main(String[] args)”方法,并判断该方法是否为public方法,然后调用JNIEnv实例的CallStaticVoidMethod方法调用该java类的main方法。
这次就简单记一下,下次会详细说JVM的内存模型和垃圾回收策略。