`
sangmin214
  • 浏览: 176633 次
  • 性别: Icon_minigender_1
  • 来自: 黄山
文章分类
社区版块
存档分类
最新评论

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觅之。

<转载结束>

 

 

分享到:
评论
1 楼 sangmin214 2011-02-18  
更新:
MANIFEST.MF没有指定Main-Class,可能是因为我是直接看Jetty的代码的原因。Jetty的发行包中的start.jar中的MANIFEST.MF是有指定Main-Class的:
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: gregw
Build-Jdk: 1.6.0_22
Main-Class: org.eclipse.jetty.start.Main
Implementation-Vendor: Eclipse.org - Jetty
Implementation-Version: 7.2.2.v20101205
url: http://www.eclipse.org/jetty

这样的话,start.jar就能直接找到Main类,然后从里面的main函数开始启动了。

相关推荐

    启动jetty的start.jar

    启动jetty的start.jar,此文件所在目录下增加lib,etc,context文件夹,lib下增加jetty的主jar包和jetty的util包。在etc下增加jetty的两个配置文件jetty.xml和webdefault.xml。在context下增加test.xml

    jetty-distribution-9.4.0.v20161208.zip

    * start.jar is jetty7主程序 * VERSION.txt * webapps应用路径,发布的引用基本都放这里 其中比较重要的目录是:etc、contexts、webapps 。个人认为可以类比 tomcat的conf、conf\Catalina\localhost、webapps目录...

    jetty start 9.2.13 项目所需要的完整jar包,免费。jetty启动调试

    jetty start 9.2.13 项目所需要的完整jar包,免费。jetty启动调试

    jettyREST:将 Jetty + Jersey + Servlet 3.0 + JAX-RS 用于 RESTful web 服务的示例

    码头休息区 jettyREST 是关于如何使用 Jetty + JAX-RS + Jersey + Servlet 3.0 作为 RESTful API 的技术堆栈的示例。 它非常基础,仅包含一...java -jar start.jar 如果在本地主机上运行,​​您可以在此处查看示例:

    jetty-7.5 资源包

    jetty 服务器软件,一个轻量级的Java Servlet 容器。使用时解压运行Start.jar 即可。

    spring-boot-reference.pdf

    10.2.6. Quick-start Spring CLI Example 10.3. Upgrading from an Earlier Version of Spring Boot 11. Developing Your First Spring Boot Application 11.1. Creating the POM 11.2. Adding Classpath ...

    jetty8.0.4

    轻量级web server,纯java, etc下可修改配置文件jetty.xml(端口等),使用java -jar start.jar启动

    Jetty嵌入项目代码中示例

    Jetty嵌入项目代码中示例,现我有一示例项目 e:/workspace/web-demo(称为project_home)...把${jetty_home}/start.jar复制到${project_home}/jetty目录下。 接下来在${project_home}/jetty/contexts目录下加一个文件。

    Jetty中文手册

    Jetty Start.jar 配置Jetty 如何设置上下文(Context Path) 如何知道使用了那些jar包 如何配置SSL 如何使用非root用户监听80端口 如何配置连接器(Connectors) 如何配置虚拟主机(Virtual Hosts) 如何配置会话ID...

    chatable-client-web:可聊天的 Web 客户端

    可聊天的网络客户端 官方 Chatable 网络聊天客户端 指示 1.编译 mvn gwt:compile ...2. 整合 创建一个 Jetty 实例 或者通过wget ... java -jar start.jar jetty.port=&lt;Your&gt;

    jetty的四个包

    jetty所需的四个基本包,包含jetty-6.1.8.jar jetty-util-6.1.8 servlet-apt-2.5-6.1.8.jar start-6.1.8.jar

    jetty-6.1.9 jspweb 服务器

    Jetty是使用Java语言编写的,它的API以一组JAR包的形式发布。开发人员可以将Jetty容器实例化成一个对象,可以迅速为一些独立运行(stand-alone)的Java应用提供网络和web连接。 Jetty 可以作为嵌入式服务器使用,...

    jetty嵌入式web容器jar包

    jetty嵌入式web容器jar包 示例: public static void main(String[] args) { // 服务器的监听端口 Server server = new Server(6666); // 关联一个已经存在的上下文 WebAppContext context = new WebAppContext...

    Linux上Solr的启动方式

     (2)使用内置的Jetty来启动Solr服务器只需要在example目录下,执行start.jar程序即可,我们可以直接执行命令:java –jar start.jar。  (3)当服务启动后,默认发布在8983端口,所以可以访问该端口来访问Solr服务,...

    azkaban-3.38安装包(已编译)

    cp ......../mysql-connector-java-5.1.38.jar ./extlib 6. 将azkaban-exec-server目录下conf、extlib目录复制到azkaban-web-server目录下 cp -a ./conf ./extlib ../azkaban-web-server 7.登录mysql,将azkaban...

    servlet-web-framework-demo:基于servlet的最小演示Web框架

    基于Servlet的最小Web框架。 例如,请参见TestController和routes 。...在码头上部署下载码头(v9.2) 将.war放到webapps路径java -jar start.jar 定位: http://localhost:8080/you-war-file-name

    Natch-JerseyMVC:Natch-JSP 的 Jersey MVC 实现,即 android-manchester.co.uk

    您可以使用 gradle jettyStart 运行它,但您需要在基本目录中使用 jetty-runner-9.1.0.M0.jar。 去做 整理上下文路径 删除注释以支持程序化 - 测试之前和之后的内存使用 为什么实时服务器上的 404 不是 404? 之后 ...

    solr-jetty:Jetty应用程序用于部署我们的Solr服务

    码头用于部署Solr的Jetty应用程序。 默认情况下,不包含任何核心。 必须将它们添加到配置中...安装下载并运行: java -jar start.jar从以下位置查看应用程序: http://localhost:8985/solr/版本号索尔4.10.4 码头8.1.16

    WebGUI:网页图形用户界面骰子

    骰子如何运行: 下载战争:Dice/artifacts/Dice.war 下载服务器: : 提取码头将 Dice.war 移动到 {jetty base}\webapps 下载安装RabbitMQ服务器: : 运行RabbitMQ服务器运行 start.jar: java -jar start.jar 转到 ==...

    URLShortener:URL Shortener-Payroc的编码采访项目

    导航到/ backend运行“ mvn软件包”以构建运行“ java -jar backend-1.0-SNAPSHOT-jar-with-dependencies.jar”以启动服务器 后端服务器在localhost:8080上运行 前端 导航到/ ui运行“ npm start”以启动服务器 ...

Global site tag (gtag.js) - Google Analytics