Jetty的启动 java -jar start.jar

Jetty的一个默认的简易启动方式是java -jar start.jar。那这个命令是如何工作的呢?

下面转载一篇文章,描述了用-jar参数时,classpath是如何工作的(http://www.zeali.net/entry/15)

<转载开始>

当用java -jar yourJarExe.jar来运行一个经过打包的应用程序的时候,你会发现如何设置-classpath参数应用程序都找不到相应的第三方类,报ClassNotFound错误。实际上这是由于当使用-jar参数运行的时候,java VM会屏蔽所有的外部classpath,而只以本身yourJarExe.jar的内部class作为类的寻找范围。

**解决方案**

一 BootStrap class扩展方案

Java 命令行提供了如何扩展bootStrap 级别class的简单方法.
-Xbootclasspath:     完全取代基本核心的Java class 搜索路径.
                                   不常用,否则要重新写所有Java 核心class
-Xbootclasspath/a: 后缀在核心class搜索路径后面.常用!!
-Xbootclasspath/p: 前缀在核心class搜索路径前面.不常用,避免
                                   引起不必要的冲突.

语法如下:
 (分隔符与classpath参数类似,unix使用:号,windows使用;号,这里以unix为例)
 java -Xbootclasspath/a:/usrhome/thirdlib.jar: -jar yourJarExe.jar

二 extend class 扩展方案

Java exten class 存放在{Java_home}\jre\lib\ext目录下.当调用Java时,对扩展class路径的搜索是自动的.总会搜索的.这样,解决的方案就很简单了,将所有要使用的第三方的jar包都复制到ext 目录下.

三 User class扩展方案

当使用-jar执行可执行Jar包时,JVM将Jar包所在目录设置为codebase目录,所有的class搜索都在这个目录下开始.所以如果使用了其他第三方的jar包,一个比较可以接受的可配置方案,就是利用jar包的Manifest扩展机制.
步骤如下:

 1.将需要的第三方的jar包,复制在同可执行jar所在的目录或某个子目录下. 比如:jar 包在 /usrhome/yourJarExe.jar 那么你可以把所有jar包复制到/usrhome目录下或/usrhome/lib 等类似的子目录下.

 2.修改Manifest 文件

 在Manifest.mf文件里加入如下行

 Class-Path:classes12.jar lib/thirdlib.jar

 Class-Path 是可执行jar包运行依赖的关键词.详细内容可以参考 http://java.sun.com/docs/books/tutorial/deployment/jar/downman.html  。要注意的是 Class-Path 只是作为你本地机器的CLASSPATH环境变量的一个缩写,也就是说用这个前缀表示在你的jar包执行机器上所有的CLASSPATH目录下寻找相应的第三方类/类库。你并不能通过 Class-Path 来加载位于你本身的jar包里面(或者网络上)的jar文件。因为从理论上来讲,你的jar发布包不应该再去包含其他的第三方类库(而应该通过使用说明来提醒用户去获取相应的支持类库)。如果由于特殊需要必须把其他的第三方类库(jar, zip, class等)直接打包在你自己的jar包里面一起发布,你就必须通过实现自定义的ClassLoader来按照自己的意图加载这些第三方类库。


以上三种方法推荐第一种 ,扩展性好,操作起来也最方便.
另外编写自己的ClassLoader,来动态载入class,是更加复杂和高级技术.限于篇幅,不赘述.有兴趣了解可以去google一下custom classloader,或者参考我的另一篇日志:让classpath参数走开

Java的安全机制随不同的JDK版本有不同的变化,会影响很多核心CLASS,比如Thread,所以很多大型商业软件,要求JDK的版本很严格.部分原因也在此.这也要求在发布自己编写的应用时候,不管大小,都要说明开发和测试的JDK版本.


本文所述方法测试基于j2sdk 1.4.2_04-b05

----------------------------------------------------------------------------------------------

附:背景知识

自JDK 1.2以后,JVM采用了委托(delegate)模式来载入class.采用这种设计的原因可以参考http://java.sun.com/docs/books/tutorial/ext/basics/load.html

归纳来讲:是基于JVM sandbox(沙盒)安装模型上提供应用层的可定制的安全机制.


Java虚拟机(JVM)寻找Class的顺序

1. Bootstrap classes

属于Java 平台核心的class,比如java.lang.String等.及rt.jar等重要的核心级别的class.这是由JVM Bootstrap class loader来载入的.一般是放置在{java_home}\jre\lib目录下

2. Extension classes

基于Java扩展机制,用来扩展Java核心功能模块.比如Java串口通讯模块comm.jar.一般放置在{Java_home}\jre\lib\ext目录下

3. User classes

开发人员或其他第三方开发的Java程序包.通过命令行的-classpath或-cp,或者通过设置CLASSPATH环境变量来引用.JVM通过放置在{java_home}\lib\tools.jar来寻找和调用用户级的class.常用的javac也是通过调用tools.jar来寻找用户指定的路径来编译Java源程序.这样就引出了User class路径搜索的顺序或优先级别的问题.

 3.1 缺省值:调用Java或javawa的当前路径(.),是开发的class所存在的当前目录
 3.2 CLASSPATH环境变量设置的路径.如果设置了CLASSPATH,则CLASSPATH的值会覆盖缺省值
 3.3 执行Java的命令行-classpath或-cp的值,如果制定了这两个命令行参数之一,它的值会覆盖环境变量CLASSPATH的值
 3.4 -jar 选项:如果通过java -jar 来运行一个可执行的jar包,这当前jar包会覆盖上面所有的值.换句话说,-jar 后面所跟的jar包的优先级别最高,如果指定了-jar选项,所有环境变量和命令行制定的搜索路径都将被忽略.JVM APPClassloader将只会以jar包为搜索范围.
有关可执行jar有许多相关的安全方面的描述,可以参考http://java.sun.com/docs/books/tutorial/jar/ 来全面了解.

这也是为什么应用程序打包成可执行的jar包后,不管你怎么设置classpath都不能引用到第三方jar包的东西了.

<转载结束>

 

start.jar里面的META-INF目录里面有个文件叫MANIFEST.MF,关于这个文件,也转载一篇介绍(http://tech.it168.com/jd/2008-07-07/200807071008995.shtml)

<转载开始>

  【IT168 技术文档】 打开Java的JAR文件我们经常可以看到文件中包含着一个META-INF目录,这个目录下会有一些文件,其中必有一个MANIFEST.MF,这个文件描述了该Jar文件的很多信息,下面将详细介绍MANIFEST.MF文件的内容,先来看struts.jar中包含的MANIFEST.MF文件内容:

 
  Manifest - Version: 1.0   Created - By: Apache Ant 1.5 . 1   Extension - Name: Struts Framework   Specification - Title: Struts Framework   Specification - Vendor: Apache Software Foundation   Specification - Version: 1.1   Implementation - Title: Struts Framework   Implementation - Vendor: Apache Software Foundation   Implementation - Vendor - Id: org.apache   Implementation - Version: 1.1   Class - Path: commons - beanutils.jar commons - collections.jar
commons - digester.jar commons - logging.jar commons - validator.jar jakarta - oro.jar
struts - legacy.jar

  如果我们把MANIFEST中的配置信息进行分类,可以归纳出下面几个大类:

  一. 一般属性

  1. Manifest-Version

  用来定义manifest文件的版本,例如:Manifest-Version: 1.0

  2. Created-By

  声明该文件的生成者,一般该属性是由jar命令行工具生成的,例如:Created-By: Apache Ant 1.5.1

  3. Signature-Version

  定义jar文件的签名版本

  4. Class-Path

  应用程序或者类装载器使用该值来构建内部的类搜索路径

  二. 应用程序相关属性

  1. Main-Class

  定义jar文件的入口类,该类必须是一个可执行的类,一旦定义了该属性即可通过 java -jar x.jar来运行该jar文件。

  三. 小程序(Applet)相关属性

  1. Extendsion-List

  该属性指定了小程序需要的扩展信息列表,列表中的每个名字对应以下的属性

  2. -Extension-Name

  3. -Specification-Version

  4. -Implementation-Version

  5. -Implementation-Vendor-Id

  5. -Implementation-URL

  四. 扩展标识属性

  1. Extension-Name

  该属性定义了jar文件的标识,例如Extension-Name: Struts Framework

  五. 包扩展属性

  1. Implementation-Title 定义了扩展实现的标题

  2. Implementation-Version 定义扩展实现的版本

  3. Implementation-Vendor 定义扩展实现的组织

  4. Implementation-Vendor-Id 定义扩展实现的组织的标识

  5. Implementation-URL : 定义该扩展包的下载地址(URL)

  6. Specification-Title 定义扩展规范的标题

  7. Specification-Version 定义扩展规范的版本

  8. Specification-Vendor 声明了维护该规范的组织

  9. Sealed 定义jar文件是否封存,值可以是true或者false (这点我还不是很理解)

  六. 签名相关属性

  签名方面的属性我们可以来参照JavaMail所提供的mail.jar中的一段

  Name: javax/mail/Address.class

  Digest-Algorithms: SHA MD5

  SHA-Digest: AjR7RqnN//cdYGouxbd06mSVfI4=

  MD5-Digest: ZnTIQ2aQAtSNIOWXI1pQpw==

  这段内容定义类签名的类名、计算摘要的算法名以及对应的摘要内容(使用BASE64方法进行编码)

  七.自定义属性

  除了前面提到的一些属性外,你也可以在MANIFEST.MF中增加自己的属性以及响应的值,例如J2ME程序jar包中就可能包含着如下信息

 
  MicroEdition - Configuration: CLDC - 1.0   MIDlet - Name: J2ME_MOBBER Midlet Suite   MIDlet - Info - URL: http: // www.javayou.com/   MIDlet - Icon: / icon.png   MIDlet - Vendor: Midlet Suite Vendor   MIDlet - 1 : mobber, / icon.png,mobber   MIDlet - Version: 1.0 . 0   MicroEdition - Profile: MIDP - 1.0   MIDlet - Description: Communicator

  关键在于我们怎么来读取这些信息呢?其实很简单,JDK给我们提供了用于处理这些信息的API,详细的信息请见java.util.jar包中,我们可以通过给JarFile传递一个jar文件的路径,然后调用JarFile的getManifest方法来获取Manifest信息。

  更详细关于JAR文件的规范请见

  http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html

  中文说明

  http://www-900.ibm.com/developerWorks/cn/java/j-jar/

<转载结束>

 

虽然start.jar里面的Main类的注释也指明MANIFEST.MF里面应该制定Main为Main-Class,不过至少7.2.2这个版本里还是没有指定。那么这个start.jar到底是怎么找到main class的呢?

这里再转载一篇讲解java启动查找main函数的文章(http://www.linuxfans.org/bbs/thread-122385-1-1.html)

不过看完了,我还是没明白这个start.jar是怎么找到main class的。

<转载开始>

java源代码分析----jvm.dll装载过程

简述
众所周知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.7 03/01/23
#
# Copyright 2003 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=&lt;jvm_dir&gt;" 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&lt;jvm.cfg中jvm名称&gt;”、“java -XXaltjvm
=&lt;jvm类型名称&gt;”或“java -J-XXaltjvm=&lt;jvm类型名称&gt;”。如果是第一种参数传递方
式,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&gt;set _JAVA_LAUNCHER_DEBUG=1
情况一
E:\work\java_research&gt;java -J-client test.ScanDirectory
----_JAVA_LAUNCHER_DEBUG----
JRE path is D:\java\j2sdk1.4.2_04\jre
jvm.cfg[0] = -&gt;-client&lt;-
jvm.cfg[1] = -&gt;-server&lt;-
jvm.cfg[2] = -&gt;-hotspot&lt;-
jvm.cfg[3] = -&gt;-classic&lt;-
jvm.cfg[4] = -&gt;-native&lt;-
jvm.cfg[5] = -&gt;-green&lt;-
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&gt;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] = -&gt;-client&lt;-
jvm.cfg[1] = -&gt;-server&lt;-
jvm.cfg[2] = -&gt;-hotspot&lt;-
jvm.cfg[3] = -&gt;-classic&lt;-
jvm.cfg[4] = -&gt;-native&lt;-
jvm.cfg[5] = -&gt;-green&lt;-
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&gt;java test.ScanDirectory
----_JAVA_LAUNCHER_DEBUG----
JRE path is D:\java\j2sdk1.4.2_04\jre
jvm.cfg[0] = -&gt;-client&lt;-
jvm.cfg[1] = -&gt;-server&lt;-
jvm.cfg[2] = -&gt;-hotspot&lt;-
jvm.cfg[3] = -&gt;-classic&lt;-
jvm.cfg[4] = -&gt;-native&lt;-
jvm.cfg[5] = -&gt;-green&lt;-
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&gt;java -J-server test.ScanDirectory
----_JAVA_LAUNCHER_DEBUG----
JRE path is D:\java\j2sdk1.4.2_04\jre
jvm.cfg[0] = -&gt;-client&lt;-
jvm.cfg[1] = -&gt;-server&lt;-
jvm.cfg[2] = -&gt;-hotspot&lt;-
jvm.cfg[3] = -&gt;-classic&lt;-
jvm.cfg[4] = -&gt;-native&lt;-
jvm.cfg[5] = -&gt;-green&lt;-
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&gt;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] = -&gt;-client&lt;-
jvm.cfg[1] = -&gt;-server&lt;-
jvm.cfg[2] = -&gt;-hotspot&lt;-
jvm.cfg[3] = -&gt;-classic&lt;-
jvm.cfg[4] = -&gt;-native&lt;-
jvm.cfg[5] = -&gt;-green&lt;-
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&gt;set path=
E:\temp&gt;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&gt;java
Usage: 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 &lt;class search path of directories and zip/jar files&gt;
    -classpath &lt;class search path of directories and zip/jar files&gt;
                  A ; separated list of directories, JAR archives,
                  and ZIP archives to search for class files.
    -D&lt;name&gt;=&lt;value&gt;
                  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[:&lt;packagename&gt;...|:&lt;classname&gt;]
    -enableassertions[:&lt;packagename&gt;...|:&lt;classname&gt;]
                  enable assertions
    -da[:&lt;packagename&gt;...|:&lt;classname&gt;]
    -disableassertions[:&lt;packagename&gt;...|:&lt;classname&gt;]
                  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觅之。

<转载结束>

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值