- 浏览: 420125 次
- 性别:
- 来自: 济南
文章分类
最新评论
-
pmh905001:
写的很详尽,感谢!
解析jvm.dll和java.exe -
Bll:
插得真深啊,我的是(eclipse_j2ee_juno):F: ...
在eclipse里jsp编译后的java和class文件的位置 -
heming_way:
谢谢,对我很有用,解答了我对多值依赖的疑问
关于多值依赖--范式! -
JavaStudy2011:
java语言解析xml文件 -
vrussell:
Thanks man, it helps me a lot!
获得IEditorPart和IDocument
简述
众所周知java.exe是java class文件的执行程序,但实际上java.exe程序只是
一个执行的外壳,它会装载jvm.dll(windows下,以下皆以windows平台为例,
linux下和solaris下其实类似,为:libjvm.so),这个动态连接库才是java
虚拟机的实际操作处理所在。本文探究java.exe程序是如何查找和装载jvm.dll
动态库,并调用它进行class文件执行处理的。
源代码
本文分析之代码,《JavaTM 2 SDK, Standard Edition, v1.4.2 fcs
Community Source Release》,可从sun官方网站下载,主要分析的源代码为:
j2se\src\share\bin\java.c
j2se\src\windows\bin\java_md.c
java.c是什么东西
'java程序’源代码
所谓'java程序’,包括jdk中的java.exe\javac.exe\javadoc.exe,java.c源
代码中通过JAVA_ARGS宏来控制生成的代码,如果该宏没定义则编译文件控制生
成java.exe否则编译文件控制生成其他的'java程序’。
比如:
j2se\make\java\javac\Makefile(这是javac编译文件)中:
$(CD) ../../sun/javac ; $(MAKE) $@ RELEASE=$(RELEASE) FULL_VERSION=$(FULL_VERSION)
j2se\make\sun\javac\javac\Makefile(由上面Makefile文件调用)中:
JAVA_ARGS = "{ \"-J-ms8m\", \"com.sun.tools.javac.Main\" }"
则由同一份java.c代码生成的javac.exe程序就会直接调用java类方法:
com.sun.tools.javac.Main,这样使其执行起来就像是直接运行的一个exe文件,
而未定义JAVA_ARGS的java.exe程序则会调用传递过来参数中的类方法。
从java.c的main入口函数说起
main()函数中前面一段为重新分配参数指针的处理。
然后调用函数:CreateExecutionEnvironment,该函数主要查找java运行环境的
目录,和jvm.dll这个虚拟机核心动态连接库文件路径所在。根据操作系统不同,
该函数有不同实现版本,但大体处理逻辑相同,我们看看windows平台该函数的处
理(j2se\src\windows\bin\java_md.c)。
CreateExecutionEnvironment函数主要分为三步处理:
a、查找jre路径。
b、装载jvm.cfg中指定的虚拟机动态连接库(jvm.dll)参数。
c、取jvm.dll文件路径。
实现:
a、查找jre路径是通过java_md.c中函数:GetJREPath实现的。
该函数首先调用GetApplicationHome函数,GetApplicationHome函数调用windows
API函数GetModuleFileName取java.exe程序的绝对路径,以我的jdk安装路径为例,
为:“D:\java\j2sdk1.4.2_04\bin\java.exe”,然后去掉文件名取绝对路径为:
“D:\java\j2sdk1.4.2_04\bin”,之后会在去掉最后一级目录,现在绝对路径为:
“D:\java\j2sdk1.4.2_04”。
然后GetJREPath函数继续判断刚刚取的路径+\bin\java.dll组合成的这个java.dll
文件是否存在,如果存在则“D:\java\j2sdk1.4.2_04”为JRE路径,否则判断取得
的“D:\java\j2sdk1.4.2_04”路径+\jre\bin\java.dll文件是否存在,存在则
“D:\java\j2sdk1.4.2_04\jre”为JRE路径。如果上面两种情况都不存在,则从注
册表中去查找(参见函数GetPublicJREHome)。
函数:GetPublicJREHome先查找
HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\CurrentVersion
键值“当前JRE版本号”,判断“当前JRE版本号”是否为1.4做为版本号,如果是则
取HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\“当前JRE版本号”
\JavaHome的路径所在为JRE路径。
我的JDK返回的JRE路径为:“D:\java\j2sdk1.4.2_04\jre”。
b、装载jvm.cfg虚拟机动态连接库配置文件是通过java.c中函数:ReadKnownVMs实现
的。
该函数首先组合jvm.cfg文件的绝对路径,JRE路径+\lib+\ARCH(CPU构架)+\jvm.cfg
ARCH(CPU构架)的判断是通过java_md.c中GetArch函数判断的,该函数中windows平
台只有两种情况:WIN64的'ia64’,其他情况都为'i386’。我的为i386所以jvm.cfg
文件绝对路径为:“D:\java\j2sdk1.4.2_04\jre\lib\i386\jvm.cfg”。文件内容如
下:
(如果细心的话,我们会发现在JDK目录中我的为:“D:\java\j2sdk1.4.2_04\jre\bin\client”和“D:\java\j2sdk1.4.2_04\jre\bin\server”两个目录下都存在jvm.dll文件。而java正是通过jvm.cfg配置文件来管理这些不同版本的jvm.dll的。)
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替代而直接抛出错误
信息。
在运行java程序时指定使用那个虚拟机的判断是由java.c中函数:CheckJvmType判断,该函数会检查java运行参数中是否有指定jvm的参数,然后从ReadKnownVMs函数读取的jvm.cfg数据结构中去查找,从而指定不同的jvm类型(最终导致装载不同jvm.dll)。
有两种方法可以指定jvm类型,
一种按照jvm.cfg文件中的jvm名称指定,
第二种方法是直接指定,它们执行的方法分别是“java -J<jvm.cfg中jvm名称>”、“java -XXaltjvm=<jvm类型名称>”或“java -J-XXaltjvm=<jvm类型名称>”。如果是第一种参数传递方式,CheckJvmType函数会取参数'-J’后面的jvm名称,然后从已知的jvm配置参数中查找如果找到同名的则去掉该jvm名称前的'-’直接返回该值;而第二种方法,会直接返回“-XXaltjvm=”或“-J-XXaltjvm=”后面的jvm类型名称;如果在运行java时未指定上面两种方法中的任一一种参数,CheckJvmType会取配置文件中第一个配置中的jvm名称,去掉名称前面的'-’返回该值。CheckJvmType函数的这个返回值会在下面的函数中汇同jre路径组合成jvm.dll的绝对路径。
比如:如果在运行java程序时使用“java -J-client test”则ReadKnownVMs会读取参数“-client”然后查找jvm.cfg读入的参数中是否有jvm名称为“-client”的,如果有则去掉jvm名称前的“-”直接返回“client”;而如果在运行java程序时使用如下参数:
“java -XXaltjvm=D:\java\j2sdk1.4.2_04\jre\bin\client test”,则ReadKnownVMs
会直接返回“D:\java\j2sdk1.4.2_04\jre\bin\client”;如果不带上面参数执行如:
“java test”,因为在jvm.cfg配置文件中第一个存在的jvm为“-client”,所以函数
ReadKnownVMs也会去掉jvm名称前的“-”返回“client”。其实这三中情况都是使用的
“D:\java\j2sdk1.4.2_04\jre\bin\client\jvm.dll”这个jvm动态连接库处理test这个class的,见下面GetJVMPath函数。
c、取jvm.dll文件路径是通过java_md.c中函数:GetJVMPath实现的。
由上面两步我们已经获得了JRE路径和jvm的类型字符串。GetJVMPath函数判断CheckJvmType
返回的jvm类型字符串中是否包含了'\’或'/’如果包含则以该jvm类型字符串+\jvm.dll作为JVM的全路径,否则以JRE路径+\bin+\jvm类型字符串+\jvm.dll作为JVM的全路径。
看看上面的例子,第一种情况“java -J-client test”jvm.dll路径为:
JRE路径+\bin+\jvm类型字符串+\jvm.dll 按照我的JDK路径则为:
“D:\java\j2sdk1.4.2_04\jre”+“\bin”+“\client”+“\jvm.dll”。
第二种情况“java -XXaltjvm=D:\java\j2sdk1.4.2_04\jre\bin\client test”路径为:
jvm类型字符串+\jvm.dll即为:“D:\java\j2sdk1.4.2_04\jre\bin\client”+“\jvm.dll”
第三种情况“java test”为:“D:\java\j2sdk1.4.2_04\jre”+“\bin”+“\client”
+“\jvm.dll”与情况一相同。所以这三种情况都是调用的jvm动态连接库“D:\java\
j2sdk1.4.2_04\jre\bin\client\jvm.dll”处理test类的。
我们来进一步验证一下:
打开cmd控制台:
设置java装载调试
E:\work\java_research>set _JAVA_LAUNCHER_DEBUG=1
情况一
E:\work\java_research>java -J-client test.ScanDirectory
----_JAVA_LAUNCHER_DEBUG----
JRE path is D:\java\j2sdk1.4.2_04\jre
jvm.cfg[0] = ->-client<-
jvm.cfg[1] = ->-server<-
jvm.cfg[2] = ->-hotspot<-
jvm.cfg[3] = ->-classic<-
jvm.cfg[4] = ->-native<-
jvm.cfg[5] = ->-green<-
299 micro seconds to parse jvm.cfg
JVM path is D:\java\j2sdk1.4.2_04\jre\bin\client\jvm.dll
2897 micro seconds to LoadJavaVM
JavaVM args:
version 0x00010002, ignoreUnrecognized is JNI_FALSE, nOptions is 2
option[ 0] = '-Djava.class.path=.'
option[ 1] = '-Dsun.java.command=test.ScanDirectory'
50001 micro seconds to InitializeJVM
Main-Class is 'test.ScanDirectory'
Apps' argc is 0
10208 micro seconds to load main class
----_JAVA_LAUNCHER_DEBUG----
usage: java test.ScanDirectory DIR [output file]
情况二
E:\work\java_research>java -XXaltjvm=D:\java\j2sdk1.4.2_04\jre\bin\client test.ScanDirectory
----_JAVA_LAUNCHER_DEBUG----
JRE path is D:\java\j2sdk1.4.2_04\jre
jvm.cfg[0] = ->-client<-
jvm.cfg[1] = ->-server<-
jvm.cfg[2] = ->-hotspot<-
jvm.cfg[3] = ->-classic<-
jvm.cfg[4] = ->-native<-
jvm.cfg[5] = ->-green<-
386 micro seconds to parse jvm.cfg
JVM path is D:\java\j2sdk1.4.2_04\jre\bin\client\jvm.dll
2795 micro seconds to LoadJavaVM
JavaVM args:
version 0x00010002, ignoreUnrecognized is JNI_FALSE, nOptions is 2
option[ 0] = '-Djava.class.path=.'
option[ 1] = '-Dsun.java.command=test.ScanDirectory'
49978 micro seconds to InitializeJVM
Main-Class is 'test.ScanDirectory'
Apps' argc is 0
9598 micro seconds to load main class
----_JAVA_LAUNCHER_DEBUG----
usage: java test.ScanDirectory DIR [output file]
情况三
E:\work\java_research>java test.ScanDirectory
----_JAVA_LAUNCHER_DEBUG----
JRE path is D:\java\j2sdk1.4.2_04\jre
jvm.cfg[0] = ->-client<-
jvm.cfg[1] = ->-server<-
jvm.cfg[2] = ->-hotspot<-
jvm.cfg[3] = ->-classic<-
jvm.cfg[4] = ->-native<-
jvm.cfg[5] = ->-green<-
381 micro seconds to parse jvm.cfg
JVM path is D:\java\j2sdk1.4.2_04\jre\bin\client\jvm.dll
3038 micro seconds to LoadJavaVM
JavaVM args:
version 0x00010002, ignoreUnrecognized is JNI_FALSE, nOptions is 2
option[ 0] = '-Djava.class.path=.'
option[ 1] = '-Dsun.java.command=test.ScanDirectory'
50080 micro seconds to InitializeJVM
Main-Class is 'test.ScanDirectory'
Apps' argc is 0
10215 micro seconds to load main class
----_JAVA_LAUNCHER_DEBUG----
usage: java test.ScanDirectory DIR [output file]
三个的JVM路径都为:
JVM path is D:\java\j2sdk1.4.2_04\jre\bin\client\jvm.dll
其他情况
E:\work\java_research>java -J-server test.ScanDirectory
----_JAVA_LAUNCHER_DEBUG----
JRE path is D:\java\j2sdk1.4.2_04\jre
jvm.cfg[0] = ->-client<-
jvm.cfg[1] = ->-server<-
jvm.cfg[2] = ->-hotspot<-
jvm.cfg[3] = ->-classic<-
jvm.cfg[4] = ->-native<-
jvm.cfg[5] = ->-green<-
377 micro seconds to parse jvm.cfg
JVM path is D:\java\j2sdk1.4.2_04\jre\bin\server\jvm.dll
2985 micro seconds to LoadJavaVM
JavaVM args:
version 0x00010002, ignoreUnrecognized is JNI_FALSE, nOptions is 2
option[ 0] = '-Djava.class.path=.'
option[ 1] = '-Dsun.java.command=test.ScanDirectory'
62382 micro seconds to InitializeJVM
Main-Class is 'test.ScanDirectory'
Apps' argc is 0
12413 micro seconds to load main class
----_JAVA_LAUNCHER_DEBUG----
usage: java test.ScanDirectory DIR [output file]
E:\work\java_research>java -XXaltjvm=D:\java\j2sdk1.4.2_04\jre\bin\server test.ScanDirectory
----_JAVA_LAUNCHER_DEBUG----
JRE path is D:\java\j2sdk1.4.2_04\jre
jvm.cfg[0] = ->-client<-
jvm.cfg[1] = ->-server<-
jvm.cfg[2] = ->-hotspot<-
jvm.cfg[3] = ->-classic<-
jvm.cfg[4] = ->-native<-
jvm.cfg[5] = ->-green<-
376 micro seconds to parse jvm.cfg
JVM path is D:\java\j2sdk1.4.2_04\jre\bin\server\jvm.dll
2937 micro seconds to LoadJavaVM
JavaVM args:
version 0x00010002, ignoreUnrecognized is JNI_FALSE, nOptions is 2
option[ 0] = '-Djava.class.path=.'
option[ 1] = '-Dsun.java.command=test.ScanDirectory'
62725 micro seconds to InitializeJVM
Main-Class is 'test.ScanDirectory'
Apps' argc is 0
8942 micro seconds to load main class
----_JAVA_LAUNCHER_DEBUG----
usage: java test.ScanDirectory DIR [output file]
由上面可以看出,如果我们安装了多个jdk或jre版本的话,使用“java -XXaltjvm=”
可以通过绝对路径指定到其他版本的jvm.dll上去,至于能不能运行还有待测试。
我们下面回到java.c的main函数中看看上面找到的jvm.dll是如何装载挂接执行的。
该操作大致分为三步:
a、装载jvm.dll动态连接库。
b、初始化jvm.dll并挂接到JNIEnv(JNI调用接口)实例。
c、调用JNIEnv实例装载并处理class类。
实现:
a、装载jvm.dll动态连接库是由main函数调用java_md.c中LoadJavaVM函数实现的。
main函数首先构造了一个InvocationFunctions结构的局部变量,InvocationFunctions
结构有两个函数指针:
typedef struct { CreateJavaVM_t CreateJavaVM; GetDefaultJavaVMInitArgs_t GetDefaultJavaVMInitArgs;} InvocationFunctions;
函数LoadJavaVM中先调用windows API函数:LoadLibrary装载jvm.dll动态连接库,
之后将jvm.dll中的导出函数JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs
挂接到InvocationFunctions变量的CreateJavaVM和GetDefaultJavaVMInitArgs函数
指针变量上。jvm.dll的装载工作宣告完成。
b、初始化jvm.dll并挂接到JNIEnv(JNI调用接口)实例是通过java.c中函数:
InitializeJVM完成的。
main方法中首先定义了一个JNIEnv结构的指针,JNIEnv结构中定义了许多与装载class
类文件、查找类方法、调用类方法有关的函数指针变量。InitializeJVM会调用上面
以挂接jvm.dll中JNI_CreateJavaVM的InvocationFunctions结构变量的CreateJavaVM方法,即调用jvm.dll中函数JNI_CreateJavaVM,该函数会将JNIEnv结构的实例返回到main中的JNIEnv结构的指针上。这样main中的JNIEnv指针获取了JNIEnv实例后,就可以开始对class文件进行处理了。
c、调用JNIEnv实例装载并处理class类。
a)如果是执行jar包。
如果执行的是一个jar包的话,main函数会调用java.c中的函数:GetMainClassName,该函数使用JNIEnv实例构造并调用java类:java.util.jar.JarFile中方法getManifest()并从返回的Manifest对象中取getAttributes("Main-Class")的值,即jar包中文件:
META-INF/MANIFEST.MF指定的Main-Class的主类名作为运行的主类。
之后main函数会调用java.c中LoadClass方法装载该主类(使用JNIEnv实例的FindClass)。
b)如果是执行class方法。
main函数直接调用java.c中LoadClass方法装载该类。
然后main函数调用JNIEnv实例的GetStaticMethodID方法查找装载的class主类中
“public static void main(String[] args)”方法,并判断该方法是否为public方法,然后调用JNIEnv实例的CallStaticVoidMethod方法调用该java类的main方法。
总结
由上面的代码分析可以看出几个问题。
a、为什么JDK和JRE不一定通过安装,直接拷到硬盘上,设置path环境变量就可以执行。因为java运行获取jre路径的首选方法正是直接通过获取java.exe绝对路径来判断的,如果通过修改注册表选项而不设置path环境变量也可以找到jre路径所在。修改方法如下:
首先我们将java.exe拷到任意目录下,我的拷到e:\temp下,在cmd中运行:
清空path环境变量
E:\temp>set path=
E:\temp>java
Error opening registry key 'Software\JavaSoft\Java Runtime Environment'
Error: could not find java.dll
Error: could not find Java 2 Runtime Environment.
导入如下注册表文件(java.reg)
Windows Registry Editor Version 5.00[HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft][HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment]"CurrentVersion"="1.4"[HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment\1.4]"JavaHome"="D:\\java\\j2sdk1.4.2_04\\jre"
再执行显示执行正常,如下:
E:\temp>javaUsage: java [-options] class [args...] (to execute a class) or java [-options] -jar jarfile [args...] (to execute a jar file)where options include: -client to select the "client" VM -server to select the "server" VM -hotspot is a synonym for the "client" VM [deprecated] The default VM is client. -cp <class search path of directories and zip/jar files> -classpath <class search path of directories and zip/jar files> A ; separated list of directories, JAR archives, and ZIP archives to search for class files. -D<name>=<value> set a system property -verbose[:class|gc|jni] enable verbose output -version print product version and exit -showversion print product version and continue -? -help print this help message -X print help on non-standard options -ea[:<packagename>...|:<classname>] -enableassertions[:<packagename>...|:<classname>] enable assertions -da[:<packagename>...|:<classname>] -disableassertions[:<packagename>...|:<classname>] disable assertions -esa | -enablesystemassertions enable system assertions -dsa | -disablesystemassertions disable system assertions
b、java.exe是通过jvm.cfg文件或直接指定jvm.dll路径来装载执行java程序的。
见上面例子。
c、不同实现版本的jvm.dll必然存在一个名为:JNI_CreateJavaVM的导出函数,
java.exe正是通过调用该函数获得JNIEnv调用接口来装载执行class类的。这个
函数也是我们下一步研究java vm实作技巧的研究出发点。
JNI_CreateJavaVM函数位于:hotspot\src\share\vm\prims\jni.cpp文件中。
[关于作者]
又志
潭州王姓,爱读聊斋,不求甚解,待业于家,百无聊赖乃取源码以读之,会有所得,
则拾而记之,游手好闲,为同侪所不齿,性尤不改,独好破解,谙习此道,今拾掇
箧中,破解软件十之又九也,中有visual slickedit10,与时所行破解版不同,行
tag files之操作而无碍,时时自得,或曰国人非尽为阿斗也,然掣肘于法律所制,
不欲示人,或有百觅而不得者,可于某之邮箱wall_john@sohu.com得之,或有爱读
源码而无偕行者,可于某之QQ:5672618觅之。
众所周知java.exe是java class文件的执行程序,但实际上java.exe程序只是
一个执行的外壳,它会装载jvm.dll(windows下,以下皆以windows平台为例,
linux下和solaris下其实类似,为:libjvm.so),这个动态连接库才是java
虚拟机的实际操作处理所在。本文探究java.exe程序是如何查找和装载jvm.dll
动态库,并调用它进行class文件执行处理的。
源代码
本文分析之代码,《JavaTM 2 SDK, Standard Edition, v1.4.2 fcs
Community Source Release》,可从sun官方网站下载,主要分析的源代码为:
j2se\src\share\bin\java.c
j2se\src\windows\bin\java_md.c
java.c是什么东西
'java程序’源代码
所谓'java程序’,包括jdk中的java.exe\javac.exe\javadoc.exe,java.c源
代码中通过JAVA_ARGS宏来控制生成的代码,如果该宏没定义则编译文件控制生
成java.exe否则编译文件控制生成其他的'java程序’。
比如:
j2se\make\java\javac\Makefile(这是javac编译文件)中:
$(CD) ../../sun/javac ; $(MAKE) $@ RELEASE=$(RELEASE) FULL_VERSION=$(FULL_VERSION)
j2se\make\sun\javac\javac\Makefile(由上面Makefile文件调用)中:
JAVA_ARGS = "{ \"-J-ms8m\", \"com.sun.tools.javac.Main\" }"
则由同一份java.c代码生成的javac.exe程序就会直接调用java类方法:
com.sun.tools.javac.Main,这样使其执行起来就像是直接运行的一个exe文件,
而未定义JAVA_ARGS的java.exe程序则会调用传递过来参数中的类方法。
从java.c的main入口函数说起
main()函数中前面一段为重新分配参数指针的处理。
然后调用函数:CreateExecutionEnvironment,该函数主要查找java运行环境的
目录,和jvm.dll这个虚拟机核心动态连接库文件路径所在。根据操作系统不同,
该函数有不同实现版本,但大体处理逻辑相同,我们看看windows平台该函数的处
理(j2se\src\windows\bin\java_md.c)。
CreateExecutionEnvironment函数主要分为三步处理:
a、查找jre路径。
b、装载jvm.cfg中指定的虚拟机动态连接库(jvm.dll)参数。
c、取jvm.dll文件路径。
实现:
a、查找jre路径是通过java_md.c中函数:GetJREPath实现的。
该函数首先调用GetApplicationHome函数,GetApplicationHome函数调用windows
API函数GetModuleFileName取java.exe程序的绝对路径,以我的jdk安装路径为例,
为:“D:\java\j2sdk1.4.2_04\bin\java.exe”,然后去掉文件名取绝对路径为:
“D:\java\j2sdk1.4.2_04\bin”,之后会在去掉最后一级目录,现在绝对路径为:
“D:\java\j2sdk1.4.2_04”。
然后GetJREPath函数继续判断刚刚取的路径+\bin\java.dll组合成的这个java.dll
文件是否存在,如果存在则“D:\java\j2sdk1.4.2_04”为JRE路径,否则判断取得
的“D:\java\j2sdk1.4.2_04”路径+\jre\bin\java.dll文件是否存在,存在则
“D:\java\j2sdk1.4.2_04\jre”为JRE路径。如果上面两种情况都不存在,则从注
册表中去查找(参见函数GetPublicJREHome)。
函数:GetPublicJREHome先查找
HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\CurrentVersion
键值“当前JRE版本号”,判断“当前JRE版本号”是否为1.4做为版本号,如果是则
取HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\“当前JRE版本号”
\JavaHome的路径所在为JRE路径。
我的JDK返回的JRE路径为:“D:\java\j2sdk1.4.2_04\jre”。
b、装载jvm.cfg虚拟机动态连接库配置文件是通过java.c中函数:ReadKnownVMs实现
的。
该函数首先组合jvm.cfg文件的绝对路径,JRE路径+\lib+\ARCH(CPU构架)+\jvm.cfg
ARCH(CPU构架)的判断是通过java_md.c中GetArch函数判断的,该函数中windows平
台只有两种情况:WIN64的'ia64’,其他情况都为'i386’。我的为i386所以jvm.cfg
文件绝对路径为:“D:\java\j2sdk1.4.2_04\jre\lib\i386\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=<jvm_dir>" 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
(如果细心的话,我们会发现在JDK目录中我的为:“D:\java\j2sdk1.4.2_04\jre\bin\client”和“D:\java\j2sdk1.4.2_04\jre\bin\server”两个目录下都存在jvm.dll文件。而java正是通过jvm.cfg配置文件来管理这些不同版本的jvm.dll的。)
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替代而直接抛出错误
信息。
在运行java程序时指定使用那个虚拟机的判断是由java.c中函数:CheckJvmType判断,该函数会检查java运行参数中是否有指定jvm的参数,然后从ReadKnownVMs函数读取的jvm.cfg数据结构中去查找,从而指定不同的jvm类型(最终导致装载不同jvm.dll)。
有两种方法可以指定jvm类型,
一种按照jvm.cfg文件中的jvm名称指定,
第二种方法是直接指定,它们执行的方法分别是“java -J<jvm.cfg中jvm名称>”、“java -XXaltjvm=<jvm类型名称>”或“java -J-XXaltjvm=<jvm类型名称>”。如果是第一种参数传递方式,CheckJvmType函数会取参数'-J’后面的jvm名称,然后从已知的jvm配置参数中查找如果找到同名的则去掉该jvm名称前的'-’直接返回该值;而第二种方法,会直接返回“-XXaltjvm=”或“-J-XXaltjvm=”后面的jvm类型名称;如果在运行java时未指定上面两种方法中的任一一种参数,CheckJvmType会取配置文件中第一个配置中的jvm名称,去掉名称前面的'-’返回该值。CheckJvmType函数的这个返回值会在下面的函数中汇同jre路径组合成jvm.dll的绝对路径。
比如:如果在运行java程序时使用“java -J-client test”则ReadKnownVMs会读取参数“-client”然后查找jvm.cfg读入的参数中是否有jvm名称为“-client”的,如果有则去掉jvm名称前的“-”直接返回“client”;而如果在运行java程序时使用如下参数:
“java -XXaltjvm=D:\java\j2sdk1.4.2_04\jre\bin\client test”,则ReadKnownVMs
会直接返回“D:\java\j2sdk1.4.2_04\jre\bin\client”;如果不带上面参数执行如:
“java test”,因为在jvm.cfg配置文件中第一个存在的jvm为“-client”,所以函数
ReadKnownVMs也会去掉jvm名称前的“-”返回“client”。其实这三中情况都是使用的
“D:\java\j2sdk1.4.2_04\jre\bin\client\jvm.dll”这个jvm动态连接库处理test这个class的,见下面GetJVMPath函数。
c、取jvm.dll文件路径是通过java_md.c中函数:GetJVMPath实现的。
由上面两步我们已经获得了JRE路径和jvm的类型字符串。GetJVMPath函数判断CheckJvmType
返回的jvm类型字符串中是否包含了'\’或'/’如果包含则以该jvm类型字符串+\jvm.dll作为JVM的全路径,否则以JRE路径+\bin+\jvm类型字符串+\jvm.dll作为JVM的全路径。
看看上面的例子,第一种情况“java -J-client test”jvm.dll路径为:
JRE路径+\bin+\jvm类型字符串+\jvm.dll 按照我的JDK路径则为:
“D:\java\j2sdk1.4.2_04\jre”+“\bin”+“\client”+“\jvm.dll”。
第二种情况“java -XXaltjvm=D:\java\j2sdk1.4.2_04\jre\bin\client test”路径为:
jvm类型字符串+\jvm.dll即为:“D:\java\j2sdk1.4.2_04\jre\bin\client”+“\jvm.dll”
第三种情况“java test”为:“D:\java\j2sdk1.4.2_04\jre”+“\bin”+“\client”
+“\jvm.dll”与情况一相同。所以这三种情况都是调用的jvm动态连接库“D:\java\
j2sdk1.4.2_04\jre\bin\client\jvm.dll”处理test类的。
我们来进一步验证一下:
打开cmd控制台:
设置java装载调试
E:\work\java_research>set _JAVA_LAUNCHER_DEBUG=1
情况一
E:\work\java_research>java -J-client test.ScanDirectory
----_JAVA_LAUNCHER_DEBUG----
JRE path is D:\java\j2sdk1.4.2_04\jre
jvm.cfg[0] = ->-client<-
jvm.cfg[1] = ->-server<-
jvm.cfg[2] = ->-hotspot<-
jvm.cfg[3] = ->-classic<-
jvm.cfg[4] = ->-native<-
jvm.cfg[5] = ->-green<-
299 micro seconds to parse jvm.cfg
JVM path is D:\java\j2sdk1.4.2_04\jre\bin\client\jvm.dll
2897 micro seconds to LoadJavaVM
JavaVM args:
version 0x00010002, ignoreUnrecognized is JNI_FALSE, nOptions is 2
option[ 0] = '-Djava.class.path=.'
option[ 1] = '-Dsun.java.command=test.ScanDirectory'
50001 micro seconds to InitializeJVM
Main-Class is 'test.ScanDirectory'
Apps' argc is 0
10208 micro seconds to load main class
----_JAVA_LAUNCHER_DEBUG----
usage: java test.ScanDirectory DIR [output file]
情况二
E:\work\java_research>java -XXaltjvm=D:\java\j2sdk1.4.2_04\jre\bin\client test.ScanDirectory
----_JAVA_LAUNCHER_DEBUG----
JRE path is D:\java\j2sdk1.4.2_04\jre
jvm.cfg[0] = ->-client<-
jvm.cfg[1] = ->-server<-
jvm.cfg[2] = ->-hotspot<-
jvm.cfg[3] = ->-classic<-
jvm.cfg[4] = ->-native<-
jvm.cfg[5] = ->-green<-
386 micro seconds to parse jvm.cfg
JVM path is D:\java\j2sdk1.4.2_04\jre\bin\client\jvm.dll
2795 micro seconds to LoadJavaVM
JavaVM args:
version 0x00010002, ignoreUnrecognized is JNI_FALSE, nOptions is 2
option[ 0] = '-Djava.class.path=.'
option[ 1] = '-Dsun.java.command=test.ScanDirectory'
49978 micro seconds to InitializeJVM
Main-Class is 'test.ScanDirectory'
Apps' argc is 0
9598 micro seconds to load main class
----_JAVA_LAUNCHER_DEBUG----
usage: java test.ScanDirectory DIR [output file]
情况三
E:\work\java_research>java test.ScanDirectory
----_JAVA_LAUNCHER_DEBUG----
JRE path is D:\java\j2sdk1.4.2_04\jre
jvm.cfg[0] = ->-client<-
jvm.cfg[1] = ->-server<-
jvm.cfg[2] = ->-hotspot<-
jvm.cfg[3] = ->-classic<-
jvm.cfg[4] = ->-native<-
jvm.cfg[5] = ->-green<-
381 micro seconds to parse jvm.cfg
JVM path is D:\java\j2sdk1.4.2_04\jre\bin\client\jvm.dll
3038 micro seconds to LoadJavaVM
JavaVM args:
version 0x00010002, ignoreUnrecognized is JNI_FALSE, nOptions is 2
option[ 0] = '-Djava.class.path=.'
option[ 1] = '-Dsun.java.command=test.ScanDirectory'
50080 micro seconds to InitializeJVM
Main-Class is 'test.ScanDirectory'
Apps' argc is 0
10215 micro seconds to load main class
----_JAVA_LAUNCHER_DEBUG----
usage: java test.ScanDirectory DIR [output file]
三个的JVM路径都为:
JVM path is D:\java\j2sdk1.4.2_04\jre\bin\client\jvm.dll
其他情况
E:\work\java_research>java -J-server test.ScanDirectory
----_JAVA_LAUNCHER_DEBUG----
JRE path is D:\java\j2sdk1.4.2_04\jre
jvm.cfg[0] = ->-client<-
jvm.cfg[1] = ->-server<-
jvm.cfg[2] = ->-hotspot<-
jvm.cfg[3] = ->-classic<-
jvm.cfg[4] = ->-native<-
jvm.cfg[5] = ->-green<-
377 micro seconds to parse jvm.cfg
JVM path is D:\java\j2sdk1.4.2_04\jre\bin\server\jvm.dll
2985 micro seconds to LoadJavaVM
JavaVM args:
version 0x00010002, ignoreUnrecognized is JNI_FALSE, nOptions is 2
option[ 0] = '-Djava.class.path=.'
option[ 1] = '-Dsun.java.command=test.ScanDirectory'
62382 micro seconds to InitializeJVM
Main-Class is 'test.ScanDirectory'
Apps' argc is 0
12413 micro seconds to load main class
----_JAVA_LAUNCHER_DEBUG----
usage: java test.ScanDirectory DIR [output file]
E:\work\java_research>java -XXaltjvm=D:\java\j2sdk1.4.2_04\jre\bin\server test.ScanDirectory
----_JAVA_LAUNCHER_DEBUG----
JRE path is D:\java\j2sdk1.4.2_04\jre
jvm.cfg[0] = ->-client<-
jvm.cfg[1] = ->-server<-
jvm.cfg[2] = ->-hotspot<-
jvm.cfg[3] = ->-classic<-
jvm.cfg[4] = ->-native<-
jvm.cfg[5] = ->-green<-
376 micro seconds to parse jvm.cfg
JVM path is D:\java\j2sdk1.4.2_04\jre\bin\server\jvm.dll
2937 micro seconds to LoadJavaVM
JavaVM args:
version 0x00010002, ignoreUnrecognized is JNI_FALSE, nOptions is 2
option[ 0] = '-Djava.class.path=.'
option[ 1] = '-Dsun.java.command=test.ScanDirectory'
62725 micro seconds to InitializeJVM
Main-Class is 'test.ScanDirectory'
Apps' argc is 0
8942 micro seconds to load main class
----_JAVA_LAUNCHER_DEBUG----
usage: java test.ScanDirectory DIR [output file]
由上面可以看出,如果我们安装了多个jdk或jre版本的话,使用“java -XXaltjvm=”
可以通过绝对路径指定到其他版本的jvm.dll上去,至于能不能运行还有待测试。
我们下面回到java.c的main函数中看看上面找到的jvm.dll是如何装载挂接执行的。
该操作大致分为三步:
a、装载jvm.dll动态连接库。
b、初始化jvm.dll并挂接到JNIEnv(JNI调用接口)实例。
c、调用JNIEnv实例装载并处理class类。
实现:
a、装载jvm.dll动态连接库是由main函数调用java_md.c中LoadJavaVM函数实现的。
main函数首先构造了一个InvocationFunctions结构的局部变量,InvocationFunctions
结构有两个函数指针:
typedef struct { CreateJavaVM_t CreateJavaVM; GetDefaultJavaVMInitArgs_t GetDefaultJavaVMInitArgs;} InvocationFunctions;
函数LoadJavaVM中先调用windows API函数:LoadLibrary装载jvm.dll动态连接库,
之后将jvm.dll中的导出函数JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs
挂接到InvocationFunctions变量的CreateJavaVM和GetDefaultJavaVMInitArgs函数
指针变量上。jvm.dll的装载工作宣告完成。
b、初始化jvm.dll并挂接到JNIEnv(JNI调用接口)实例是通过java.c中函数:
InitializeJVM完成的。
main方法中首先定义了一个JNIEnv结构的指针,JNIEnv结构中定义了许多与装载class
类文件、查找类方法、调用类方法有关的函数指针变量。InitializeJVM会调用上面
以挂接jvm.dll中JNI_CreateJavaVM的InvocationFunctions结构变量的CreateJavaVM方法,即调用jvm.dll中函数JNI_CreateJavaVM,该函数会将JNIEnv结构的实例返回到main中的JNIEnv结构的指针上。这样main中的JNIEnv指针获取了JNIEnv实例后,就可以开始对class文件进行处理了。
c、调用JNIEnv实例装载并处理class类。
a)如果是执行jar包。
如果执行的是一个jar包的话,main函数会调用java.c中的函数:GetMainClassName,该函数使用JNIEnv实例构造并调用java类:java.util.jar.JarFile中方法getManifest()并从返回的Manifest对象中取getAttributes("Main-Class")的值,即jar包中文件:
META-INF/MANIFEST.MF指定的Main-Class的主类名作为运行的主类。
之后main函数会调用java.c中LoadClass方法装载该主类(使用JNIEnv实例的FindClass)。
b)如果是执行class方法。
main函数直接调用java.c中LoadClass方法装载该类。
然后main函数调用JNIEnv实例的GetStaticMethodID方法查找装载的class主类中
“public static void main(String[] args)”方法,并判断该方法是否为public方法,然后调用JNIEnv实例的CallStaticVoidMethod方法调用该java类的main方法。
总结
由上面的代码分析可以看出几个问题。
a、为什么JDK和JRE不一定通过安装,直接拷到硬盘上,设置path环境变量就可以执行。因为java运行获取jre路径的首选方法正是直接通过获取java.exe绝对路径来判断的,如果通过修改注册表选项而不设置path环境变量也可以找到jre路径所在。修改方法如下:
首先我们将java.exe拷到任意目录下,我的拷到e:\temp下,在cmd中运行:
清空path环境变量
E:\temp>set path=
E:\temp>java
Error opening registry key 'Software\JavaSoft\Java Runtime Environment'
Error: could not find java.dll
Error: could not find Java 2 Runtime Environment.
导入如下注册表文件(java.reg)
Windows Registry Editor Version 5.00[HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft][HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment]"CurrentVersion"="1.4"[HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment\1.4]"JavaHome"="D:\\java\\j2sdk1.4.2_04\\jre"
再执行显示执行正常,如下:
E:\temp>javaUsage: java [-options] class [args...] (to execute a class) or java [-options] -jar jarfile [args...] (to execute a jar file)where options include: -client to select the "client" VM -server to select the "server" VM -hotspot is a synonym for the "client" VM [deprecated] The default VM is client. -cp <class search path of directories and zip/jar files> -classpath <class search path of directories and zip/jar files> A ; separated list of directories, JAR archives, and ZIP archives to search for class files. -D<name>=<value> set a system property -verbose[:class|gc|jni] enable verbose output -version print product version and exit -showversion print product version and continue -? -help print this help message -X print help on non-standard options -ea[:<packagename>...|:<classname>] -enableassertions[:<packagename>...|:<classname>] enable assertions -da[:<packagename>...|:<classname>] -disableassertions[:<packagename>...|:<classname>] disable assertions -esa | -enablesystemassertions enable system assertions -dsa | -disablesystemassertions disable system assertions
b、java.exe是通过jvm.cfg文件或直接指定jvm.dll路径来装载执行java程序的。
见上面例子。
c、不同实现版本的jvm.dll必然存在一个名为:JNI_CreateJavaVM的导出函数,
java.exe正是通过调用该函数获得JNIEnv调用接口来装载执行class类的。这个
函数也是我们下一步研究java vm实作技巧的研究出发点。
JNI_CreateJavaVM函数位于:hotspot\src\share\vm\prims\jni.cpp文件中。
[关于作者]
又志
潭州王姓,爱读聊斋,不求甚解,待业于家,百无聊赖乃取源码以读之,会有所得,
则拾而记之,游手好闲,为同侪所不齿,性尤不改,独好破解,谙习此道,今拾掇
箧中,破解软件十之又九也,中有visual slickedit10,与时所行破解版不同,行
tag files之操作而无碍,时时自得,或曰国人非尽为阿斗也,然掣肘于法律所制,
不欲示人,或有百觅而不得者,可于某之邮箱wall_john@sohu.com得之,或有爱读
源码而无偕行者,可于某之QQ:5672618觅之。
发表评论
-
boolean占用几个字节
2010-10-24 18:36 2174Although the Java virtual machi ... -
PermGen space的分析和解决方法
2010-08-26 21:45 1263PermGen space的全称是Permanent Gene ... -
学习jdk jre笔记
2010-08-19 17:57 1355JDK(Java 开发套件) JRE(Java ... -
双检锁DLC(转)
2010-08-16 20:48 1094对于双检锁,其实有多种不同的用法,有很多种用法是无论如何不会出 ... -
visualVM中各个名词的解释
2010-08-10 15:01 2222Process Memory Parameters De ...
相关推荐
在Windows操作系统中,Java开发和运行涉及多个进程和组件,其中`java.exe`、`javaw.exe`、`javaws.exe`以及`jvm.dll`各自扮演着不同的角色。了解这些区别有助于更好地管理和优化Java应用程序的运行。 1. **java.exe...
JVM DLL文件是Java虚拟机在Windows平台上运行时所需的重要组件之一,通常包含了一组必要的函数接口,用于启动和管理JVM实例。 ### 二、文件路径及版本变更说明 文件路径:`C:\Program Files\Java\jre1.5.0_17\bin\...
"jdic.dll"是Java环境中一个可能涉及汉字处理和字典功能的动态链接库,有32位和64位两种版本。它的正确使用和管理对于依赖此库的Java应用程序的正常运行至关重要。遇到与jdic.dll相关的错误时,应按照上述步骤进行...
msjava.dll是微软自家实现的Java虚拟机的一部分,用于解析和执行Java Applets和Java应用程序。这些Applets通常嵌入到网页中,允许用户在浏览器上运行交互式Java小应用程序。 **知识点三:msjava.dll的兼容性问题** ...
hsdis-amd64.dll和hsdis-i386.dll是与Java虚拟机(JVM)相关的动态链接库文件,主要用于Java性能分析和调试。这两个文件分别对应于AMD64(也称为x86_64或64位)和i386(32位)架构的处理器。在Java世界中,JVM是运行...
这个动态链接库的作用在于,当Java程序运行时,它能够解析并转换JVM执行的字节码指令,呈现为汇编语言的形式,这对于理解JVM如何执行Java代码非常有帮助。 在Java程序运行时,我们可以通过JDK的命令行工具如jinfo...
下面将详细解析JVM对.dll文件的装载过程以及对Java类的装载过程。 首先,了解JVM对.dll文件的装载过程,这包括几个关键步骤: 1. 创建JVM装载环境和配置:JVM启动时首先会创建一个运行环境,这包括分配内存、设置...
“dll”和“DLL工具.exe”表明可能包含一个用于处理或修复DLL问题的实用程序。在Windows系统中,DLL文件有时会丢失或损坏,导致程序无法正常运行。DLL工具可以检测并解决这些问题,确保libsvnjavahl-1.dll能正确加载...
hsdis-amd64.dll是一款专门针对Windows 10 64位系统设计的Java插件,其主要功能是将Java虚拟机(JVM)执行的字节码转化为汇编语言格式,这对于理解Java程序的底层运行机制、进行性能分析和优化具有重要意义。...
JavaService.exe-install MyTestService "%JAVA_HOME%\jre\bin\server\jvm.dll" \ -Djava.class.path="%JAVA_HOME%\lib\tools.jar;F:\MyTest\lib\hibernate3.jar; \ F:\MyTest\lib\jackson-all-1.7.1.jar;F:\...
2. 验证JVM.dll的存在和位置:Java JRE应该会将必要的dll文件安装到系统的适当目录下,通常是`%JAVA_HOME%\bin`。如果找不到,可能需要重新安装JRE。 3. 修复MSVCR71.DLL:由于描述中提到了这个问题,可能是因为...
1. **jcom.dll**:这是一个动态链接库(DLL),它是Jacob与Java虚拟机(JVM)进行通信的关键。它实现了Java Native Interface (JNI),使得Java代码能够调用C++编写的COM组件。 2. **jacob.dll**:这是Jacob的主要...
为了更好地理解和优化JVM的运行效率,开发者有时需要查看JVM如何将Java字节码转化为机器可执行的汇编指令。hsdis-i368.dll就是这样一款工具,它是专门为x86架构的Windows系统设计的,用于帮助开发者打印出JIT(Just-...
描述中提到的“JAVA 使用JCO 需要用的dll包”,意味着在Java开发环境中,为了利用SAP JCo的功能,需要将“sapjco3.dll”配置到系统路径中,以便Java虚拟机(JVM)能够识别并调用它。这个dll文件提供了必要的接口,...
《深入解析JNative:连接Java与C++的桥梁》 JNative,作为一款强大的库,为Java程序员提供了与C++代码交互的能力。它允许Java应用程序调用C++编写的动态链接库(DLL或.so),从而扩展了Java的功能,特别是在处理...
5. j2ewiz.exe:这是Java2Exe的主执行程序,负责处理整个转换流程,包括读取配置、解析JAR文件、打包JVM以及生成最终的.exe文件。 6. config.exe:这可能是一个配置工具,让用户能够图形化地设置Java2Exe的转换参数...
10. **ExportXFDF.exe**:这是一个可执行文件,可能用于将PDF表单数据导出为XFDF(XML格式的表单数据交换格式),便于数据处理和传输。 通过这些组件,开发者可以实现诸如读取PDF文本、提取图像、创建新的PDF文档、...
在一般Java程序中加载.dll文件,首先要明确JVM的搜索路径,即`java.library.path`系统属性。默认情况下,它与系统环境变量PATH相同。可以通过以下方式设置: 1. 修改系统环境变量PATH,然后重启Eclipse。 2. 在Java...