java打包成jar文件run指令
000
前段时间由于研究原来广为传播的String和StringBuffer的性能问题,
自己做了几个小实验并得出一些结论,但是从网友的反应来看那个研究并没有
起到应有的目的,而且网友也很中肯的提出了自己的意见并对实验中的一些内
容指出了其缺陷,针对他们的反应我又反编译了代码来进行对比,但是几位网
友仍然不是很信服,而且上次实验的结果和反编译得到的结论并不能完全吻合,
因为反编译代码的对比基本上是基于语句的多少,因此这个这个对比也确实不
能使人信服,但是这给我的下一步行动指引了方向:研究JVM指令和JVM结构,
在对反编译后的代码有完全的理解才能给出可能使人信国防教育图片 服的结论。
本文以及以后将要写的一些文章就是我研究JVM规范的一些心得,我希望
在和大家共同理解的基础上进行我们下一轮的深入研究。
好,闲话少说,开始我们的正文。
JVM执行的对象就是大家非常熟悉的class文件,我们也称为类文件,
JVM规范定义的这个编译完成的代码文件(虽然并非强制要求是实际的文件)的
格式非常的详实,但是我们这里只说一些宏观的内容,以后有机会再研究细节
的内容吧。JVM要求的类文件的格式是和硬件和操作系统无关的一种二进制格
式,它精确定义了类或者接口的表示,它甚至包含了字节顺序这样的细节,而
字节顺序在特定平台的目标文件格式中一般都是固定的,不会进行说明。
JVM所支持的数据类型和Java语言规范中定义的几乎一样,请注意是几
乎一样!也就是原始类型和引用类型,他们可以被存储在变量表中,也可以作
为参数传递、被方法返回,更通常的就是成为操作的对象。为什么和Java语
言规范中定义的不完全一样呢?因为JVM中有一种Java语言所没有的原始类型:
返回地址类型(returnAddresstype)。该类型是jsr,ret以及jsr_w指令需
要使用到的,它的值是JVM指令的操作码的指针,并且它的值是不能被运行中
的程序所修改的。
另外需要提到的就是布尔类型的值,虽然在Java语言中它是完全独立的值,
但是在JVM中只提供了对它的有限支持,表现在:
没有单独的操作布尔类型的指令,源代码中的布尔类型的操作在编译以后
是作为int类型的值进行操作的。
JVM直接支持布尔数组,newarray指令可以创建布尔数组,而它的访问和
修改操作却是使用byte类型的数组的操作指令进行的:baload,bastore。(在
JDK1.0,1,1以及1.2中,布尔数组被编码为byte数组,每个元素是8位)JVM
用1代表true,用0代表fal,编译器将源代码中的布尔类型映射为JVM中
的int类型,而且必须和JVM的要求一致。
另外JVM规范中对于浮点类型的数据有大段的说明,我没有怎么看,主要
是讨论JVM的浮点型和IEEE754的关系的。
关于类型的另外一个需要提一下的是类型检查。JVM期望几乎所有的类型
检查已经在运行之前完成了(通常是由编译器进行检查的)而不用JVM自己来检
查。原始类型的值不需要被标记或者在运行时被检查以确定他们的类型,同样
他们也不用和引用类型的值进行区分,区分工作是由JVM的指令集来完成的,
JVM的指令集使用不同指令来区分它要操作的值的类型,例如iadd,ladd,
fadd以及dadd是用于将两个数字相加并产生数字类型结果的所有JVM指令,
但是每个指令都是针对特定类型的,分别对应int,long,float以及double。
JVM包含对对象的显式支持。类是动态分配的类实例或者是一个数组,JVM
中的引用类型就是对一个对象的引用,引用类型的值可以想象为对象的指针,
一个对象同时可能存在多个对它的引用,对象总是通过引用被操作、传递或者
测试的。
对于引用类型,需要提及的一点就是关于null,它最初是没有运行时类型
的,但是它可以被转换为任何类型,而且对于null,JVM并没有要求任何梦见给孩子喂奶 具体
的值与之对应。
说完上面这些,我们就开始进入我学习JVM时最想了解的部分了,大家可
要打起精神哦。
JVM为运行一个程序定义了几种数据区(DataArea),包括:pc寄存器、
JVM堆栈、堆、方法区(MethodArea)、运行时常量池(RuntimeConstantPool)
以及本机方法堆栈(NativeMethodStacks),这些数据区根据其生存期可以分
为两种,一种就是和JVM的生存期相同(包括堆和方法区),一种和线程的生存
期相同(其它的),和JVM生存期相同的数据区在JVM启动的时候被创建并在
JVM退出的时候被销毁,而和线程生存期相同的数据区是每个线程一个的,他
们在线程创建的时候被创建,在线程被销毁的时候被销毁。
由于JVM可以同时支持运行多个线程,因此每个线程必然需要各自的
PC(programcounter)寄存器,无论从什么角度讲,每个JVM线程只能在一个时
间只能执行一个方法,该方法也就是线程的当前方法,如果该方法不是本机方
法,那么PC寄存器保存的就是当前指令(JVM的指令)的地址,如果是当前方法
是本机方法,PC寄存器的值就没有被定义。JVM的PC寄存器的大小足够大,可
以容纳一个returnAddress类型或者特定平台的本机指针。
每个JVM线程还拥有一个私有的JVM堆栈,它存储帧(下一篇文章会讲到)。
JVM堆栈和像C这样的传统编程语言中的堆栈是类似的,它保存局部变量和部
分结果,并且在方法调用和返回中也担任一些职责。因为除了对帧的压入和弹
出操作外,对JVM堆栈不能直接进行操作,因此帧可能是在堆上分配的。如果
一个线程中计算所需的JVM堆栈大于允许的大小,JVM会抛出
StackOverflowError错误,如果JVM堆栈是可以动态伸缩的,如果需要扩展,
但是又没有足够的内存可用或者没有足够的内存为一个新线程创建JVM堆栈,
JVM会抛出OutOfMemoryError错误。
JVM只有一个为所有线程所共享的堆,所有的类实例和数组都是在堆中创
建的。堆所存储的对象被一个自动存储管理系统回收(也就是我们所熟知的垃圾
收集器(gc))。对象不能被显式的释放,JVM假设没有特定类型的自动存储管
理系统,存储管理技术可以根据实现者的系统需求进行选择。如果计算所需的
内存堆大于自动存储管理系统可以使用的大小,JVM会抛出OutOfMemoryError
错误。
JVM只有一个为所有的线程所共享的方法区,方法区类似传统语言的已编
译代码的存储区或者UNIX进程的“文本”段。它存储类结构,例如运行时常
量池,成员和方法数据以及方法、构造方法的代码(包括用于类和实例的初始化
以及接口类型初始化的特定方法(这些特定方法以后会讲到))。虽然从逻辑上
讲方法区是堆的一部分,但是JVM的简单实现可以选择不对方法区进行垃圾收
集或者压缩(以笔者的理解就是类不能进行卸载)。最新版本(第二版)的JVM规
范没有要求方法区的位置或者管理已编译代码的策略。如果方法区的内存不能
满足一个分配请求,JVM会抛出OutOfMemoryError。
运行时常量池是类文件中的常量池表的运行时表示,它包含几种常量,范围
从编译时就已知的数字常量到运行时必须进行解析的方法和成员引用。运行时
常量池扮演的功能类似于传统编程语言中的符号表(symboltable),但是它所
包含的数据比典型的符号表更多。
每个运行时常量池时从JVM的方法区中分配的,对于特定方法或者接口的
运行时常量池是JVM在创建类或者接口的时候创建的。
当创建一个类或者接口时,如果创建运行时常量池需要的内存比方法区中
的可用内容更多的内存,JVM会抛出OutOfMemoryError。
关于常量池创建的更多内容以后可能会更详细的讲解。
JVM的实现可能使用传统的堆栈(更通常的讲就是C栈)以支持本机方法
(不是使用JAVA语言编写的方法),本机方法堆栈也可以用于在像C语言这样的
语言中为JVM指令集实现解析器,对于不能加载本机方法以及自身不依赖传统
堆栈的JVM实现而言,它可以不提供本机方法堆栈,如果提供,本机方法堆栈
通常在线程创建的时候为每个线程分配(以笔者的理解应该是需要使用本机方
法的线程)。如果线程计算所需的内存比本机方法堆栈所允许的大,JVM会抛出
StackOverflowError错误,如果本机方法堆栈可以动态伸缩,而当需要扩展的
时候又没有足够的内存时,或者没有足够的内容用于创建一个本机方法堆栈,
JVM会抛出OutOfMemoryError。
对于上面的这些数据区,JVM规范允许它们的大小是固定尺寸的,也可以
是根据计算的需要动态伸缩的,如果是固定尺寸的,其尺寸可以在创建时自主
选择。JVM的实现可以给程序员或者用户提供控制JVM堆栈的初始大小的方法,
同样,在动态伸缩的情况下可以控制最大大小和最小大小,并且它们所使用的
内存空间可以不是连续的。
下面通过对JVM堆栈的帧的详细介绍了解方法执行的一些内幕。
帧通常用于存储数据和部分结果,同时还用于执行动态链接、返回方法的
返回值以及分发异常。
帧在方法调用的时候被创建,在方法完成的时候销毁。它是在创建它的线
程的JVM堆栈中分配到空间的,每个帧都有它自己的局部变量数组、操作数猪脚的做法 堆
栈和一个当前方法所在的类的运行时常量池的引用。
它的局部变量数组和操作数堆栈的大小是在编译的时候就确定了的,而且
它是和它所联系的方法的代码一起提供的,因此它的数据结构的尺寸仅仅依赖
于JVM的实现和方法调用时同时可以分配的内存。
对于正在执行的方法而言只有一个帧是活动的,这个帧就是所谓的当前帧,
它的方法就是当前方法,当前方法所在的类被定义为当前类。局部变量和操作
数堆喝酒的句子 栈的操作通常和当前帧有关。
如果一个帧所在的方法调用了另外的方法或者方法结束,那么该帧不再是
当前帧。如果是调用另外的方法,那么一个新的帧会被创建并且在控制权转换
到新方法时成为当前帧;如果是方法结束,如果有方法返回,当前帧将它的方
法调用的结果传递给前一个帧,当前一个帧成为当前帧时当前帧被丢弃。
需要注意的是由一个线程创建的帧是局部于该线程的,其它的线程不能引
用它。
每个帧都包含变量数组,也就是我们所熟知的局部变量数组。一个局部变
量可以保存一个boolean、byte、char、short、int、float、引用或者
returnAddress值,一对局部变量才能保存一个long或者double值。
局部变量是根据索引进行寻址的,第一个局部变量的索引是0。如果一个
整型值介于0和局部变量数组的长度之间并且也只有在这个区间的时候它才会
被作为局部变量数组的索引。
long型或者double型的值占用两个连续的局部变量,这样的值可能只能
使用较小的那个索引值进行寻址,例如,局部变量数组中索引为n的double
变量值实际上占用n和n+1,但是局部变量n+1是不能读取的,它可以被写入,
但是这样做会使得局部变量n的内容无效。JVM没有要求n是偶数,这就意味
着double和long型值在局部变量数组中不必是64位对齐的,JVM的实现者可
以决定使用适当的方式表示那样的值。
JVM使用局部变量传递方法调用的参数,对于类方法调用(也就是static
方法),所有的参数都是连续的存储在局部变量表中并且是从0开始的,对于实
例方法调用,所有的参数也是连续的但是是从1开始的,局部变量0存储的是
实例方法所在的类实例的引用。
每个帧都包含一个后进先出的堆栈,也就是它的操作数堆栈。
操作数堆栈在刚刚被创建的时候是空的,JVM提供指令从局部变量或者成
员加载常量或者值到堆栈,其它的JVM指令从操作数堆栈提取操作数,操作它
们并将结果放回操作数堆栈。操作数堆栈也用于准备传递给方法的参数以及接
收方法的结果。
例如一个iadd指令将两个int值相加,该指令要求它的前一条指令将它要
相加的两个值压入操作数堆栈的最上面,它从操作数堆栈取出那两个值进行相
加并将结果放回操作数堆栈。
子计算可能是嵌套在操作数堆栈中的,产生的值可以被嵌入的计算使用。
操作数堆栈的每一项都可以保存JVM的任何类型的值,包括long和
double型的。
操作数堆栈中的值必须根据其类型进行操作。下面的这些情况都是不可能
的:压入两个int值而后续的操作将它们作为long型或者压入两个float值而
后续的操作是iadd指令(该指令的操作对象是两个int型)。有一小部分JVM指
令(例如dup和swap)将运行时数据区的值作为原始的值(rawvalue)进行操作
而不考虑其类型,这些指令是以一种不能用于修改或者分解单独的值的方式定
义的,这些对操作数堆栈操作的限制通过类文件验证进行了强制。
在任何时候操作数堆栈都有其相应的深度,long或者double型的值是两个
单位而其它的值是一个单位。
每个帧都包含一个相应于当前方法的类型的运行时常量池的引用以支持方
法代码的动态链接。类文件代码中的方法代码指的是被调用的方法以及通过符
号引用可以访问的变量,动态链接将这些符号方法引用翻译为具体的方法引用、
在必要的时候加载类以解析未定义的符号以及将变量访问翻译为那些变量的运
行时位置在存储结构中的适当的偏移。方法和变量的晚期绑定使得方法使用到
的其它类的变化可以破坏该代码的可能性更小。
如果方法调用没有导致一个异常(无论是JVM抛出的还是代码显式抛出的)
就被认为是方法调用正常结束。如果当前方法调用正常结束,那么一个值可能
被返回给调用它的方法。
在这种情况下,当前帧被用于恢复调用者的状态,包括它的局部变量和操
作数堆栈以及适当增加程序计数器以跳过方法调用指令。方法调用者所在的帧
的程序的执行正常的继续,如果有方法返回,返回值被压入帧的操作数堆栈。
如果方法里面的一个JVM指令的执行雨的古诗 引起JVM抛出一个异常并且那个异常
在方法里面没有被处理就会导致方法调用突然结束,执行一个athrow指令也可
以导致一个异常被显式的抛出并且如果那个异常没有被当前方法捕获也可以导
致方法调用突然结束,写景好段 一个突然结束的方法调用永远也不会向它的调用者返回
一个值。
一个帧可能会被像调试信息这样的与实现相关的特定信息扩展。
常常在网上看到有人询问:如何把java程序编译成.exe文件。通常回
答只有两种,一种是制作一个可执行的JAR文件包,然后就可以像.chm文档
一样双击运行了;而另一种是使用JET来进行编译但是JET是要用钱买的,
而且据说JET也不是能把所有的Java程序都编译成执行文件,性能也要打些
折扣。所以,使用制作可执行JAR文件包的方法就是最佳选择了,何况它还能
保持Java的跨平台特性。
下面就来看看什么是JAR文件包吧:
文件包
JAR文件就是JavaArchiveFile,顾名思意,它的应用是与Java息息
相关的,是Java的一种文档格式。JAR文件非常类似ZIP文件——准确的说,
它就是ZIP文件,所以叫它文件包。JAR文件与ZIP文件唯一的区别就是在
JAR文件的内容中,包含了一个MET封土 A-INF/文件,这个文件是在
生成JAR文件的时候自动创建的。举个例子,如果我们具有如下目录结构的一
些文件:
==
`--test
`--
把它压缩成ZIP文件,则这个ZIP文件的内部目录结构为:
`--test
`--
如果我们使用JDK的jar命令把它打成JAR文件包,则这个
JAR文件的内部目录结构为:
|--META-INF
|`--
`--test
`--
2.创建可执行的JAR文件包
制作一个可执行的JAR文件包来发布你的程序是JAR文件包最典型的用
法。
Java程序是由若干个.class文件组成的。这些.class文件必须根据
它们所属的包不同而分级分目录存放;运行前需要把所有用到的包的根目录指
定给CLASSPATH环境变量或者java命令的-cp参数;运行时还要到控制台
下去使用java命令来运行,如果需要直接双击运行必须写Windows的批处理
文件(.bat)或者Linux的Shell程序。因此,许多人说,Java是一种方便
开发者苦了用户的程序设计语言。
其实不然,如果开发者能够制作一个可执行的JAR文件包交给用户,那
么用户使用起来就方便了。在Windows下安装JRE(JavaRuntime
Environment)的时候,安装文件会将.jar文件映射给打开。那
么,对于一个可执行的JAR文件包,用户只需要双击它就可以运行程序了,和
阅读.chm文档一样方便(.chm文档默认是由打开的)。那么,现在
的关键,就是如何来创建这个可执行的JAR文件包。
创建可执行的JAR文件包,需要使用带cvfm参数的jar命令,同样以
上述test目录为例,命令如下:
这里和两个文件,分别是对应的参数f和m,
其重头戏在。因为要创建可执行的JAR文件包,光靠指定一个
文件是不够的,因为MANIFEST是JAR文件包的特征,可执行的
JAR文件包和不可执行的JAR文件包都包含MANIFEST。关键在于可执行JAR
文件包的MANIFEST,其内容包含了Main-Class一项。这在MANIFEST中书写
格式如下:
Main-Class:可执行主类全名(包含包名)
例如,假设上例中的是属于test包的,而且是可执行的类
(定义了publicstaticvoidmain(String[])方法),那么这个
可以编辑如下:
Main-Class:<回车>
这个可以放在任何位置,也可以是其它的文件名,只需要有
Main-Class:一行,且该行以一个回车符结束即可。创建了
文件之后,我们的目录结构变为:
==
|--test
|`--
`--
这时候,需要到test目录的上级目录中去使用jar命令来创建JAR文
件包。也就是在目录树中使用“==”表示的那个目录中,使用如下命令:
之后在“==”目录中创建了,这个就是执行的JAR
文件包。运行时只需要使用命令即可。
需要注意的是,创建的JAR文件包中需要包含完整的、与Java程序的
包结构对应的目录结构,就像上例一样。而Main-Class指定的类,也必须是
完整的、包含包路径的类名,如上例的;而且在没有打成JAR文件
包之前可以使用java<类名>来运行这个类,即在上例中是
可以正确运行的(当然要在CLASSPATH正确的情况下)。
命令详解
jar是随JDK安装的,在JDK安装目录下的b大学啥时候开学 in目录中,Windows下文
件名为,Linux下文件名为jar。它的运行需要用到JDK安装目录下
lib目录中的文件。不过我们除了安装JDK什么也不需要做,因
为SUN已经帮我们做好了。我们甚至不需要将放到CLASSPATH中。
使用不带任何的jar命令我们可以看到jar命令的用法如下:
jar{ctxu}[vfm0M][jar-文件][manifest-文件][-C目录]文件名...
其中{ctxu}是jar命令的子命令,每次jar命令只能包含ctxu中的
一个,它们分别表示:
-c创建新的JAR文件包
-t列出JAR文件包的内容列表
-x展开JAR文件包的指定文件或者所有文件
-u更新已存在的JAR文件包(添加文件到JAR文件包中)
[vfm0M]中的选项可以任选,也可以不选,它们是jar命令的选项
参数
-v生成详细报告并打印到标准输出
-f指定JAR文件名,通常这个参数是必须的
-m指定需要包含的MANIFEST清单文件
-0只存储,不压缩,这样产生的JAR文件包会比不用该参数产生的体
积大,但速度更快
-M不产生所有项的清单(MANIFEST〕文件,此参数会忽略-m参数
[jar-文件]即需要生成、查看、更新或者解开的JAR文件包,它
是-f参数的附属参数
[manifest-文件]即MANIFEST清单文件,它是-m参数的附属参
数
[-C目录]表示转到指定目录下去执行这个jar命令的操作。它相
当于先使用cd命令转该目录下再执行不带-C参数的jar命令,它只能在创
建和更新JAR文件包的时候可用。
文件名...指定一个文件/目录列表,这些文件/目录就是要添加到JAR
文件包中的文件/目录。如果指定了目录,那么jar命令打包的时候会自动把
该目录中的所有文件和子目录打入包中。
下面举一些例子来说明jar命令的用法:
1)t
该命令没有执行过程的显示,执行结果是在当前目录生成了文
件。如果当前目录已经存在,那么该文件将被覆盖。
2)t
该命令与上例中的结果相同,但是由于v参数的作用,显示出了打包过程,
如下:
标明清单(manifest)
增加:test/(读入=0)(写出=0)(存储了0%)
增加:test/(读入=7)(写出=6)(压缩了14%)
3)t
该命令与2)结果类似,但在生成的中没有包含META-
INF/MANIFEST文件,打包过程的信息也略有差别:
增加:test/(读入=0)(写出=0)(存储了0%)
增加:test/(读入=7)(写出=6)(压缩了14%)
4)
运行结果与2)相似,显示信息也相同,只是生成JAR包中的META-
INF/MANIFEST内容不同,是包含了的内容
5)
在已经存在的情况下,可以查看中的内容,如对于
2)和3)生成的分别应该此命令,结果如下;
对于2)
META-INF/
META-INF/宣誓口号
test/
test/
对于3)
test/
test/
6)
除显示5)中显示的内容外,还包括包内文件的详细信息,如:
0WedJun1915:39:06GMT2002META-INF/
86WedJun1915:39:06GMT2002META-INF/
0WedJun1915:33:04GMT2002test/
7WedJun1915:33:04GMT2002test/
7)
解开到当前目录,不显示任何信息,对于2)生成的,
解开后的目录结构如下:
==
|--META-INF
|`--MANIFEST
`--test
`--
运行结果与7)相同,对于解压过程有详细信息显示,如:
创建:META-INF/
展开:META-INF/
创建:test/
展开:test/
9)
在中添加了文件,此使用jartf来查看
可以发现中比原来多了一个manifest。这里顺便提一下,
如果使用-m参数并指定文件,那么是作为清单
文件MANIFEST来使用的,它的内容会被添加到MANIFEST中;但是,如果作
为一般文件添加到JAR文件包中,它跟一般文件无异。
10)
与9)结果相同,同时有详细信息显示,如:
增加:(读入=17)(写出=19)(压缩了-11%)
4.关于JAR文件包的一些技巧
1)使用unzip来解压JAR文件
在介绍JAR文件的时候就已经说过了,JAR文件实际上就是ZIP文件,
所以可以使用常见的一些解压ZIP文件的工具来解压JAR文件,如Windows
下的WinZip、WinRAR等和Linux下的unzip等。使用WinZip和WinRAR
等来解压是因为它们解压比较直观,方便。而使用unzip,则是因为它解压时
可以使用-d参数指定目标目录。
在解压一个JAR文件的时候是不能使用jar的-C参数来指定解压的目
标的,因为-C参数只在创建或者更新包的时候可用。那么需要将文件解压到
某个指定目录下的时候就需要先将这具JAR文件拷贝到目标目录下,再进行解
压,比较麻烦。如果使用unzip,就不需要这么麻烦了,只需要指定一个-d
参数即可。如:
-ddest/
2)使用WinZip或者WinRAR等工具创建JAR文件
上面提到JAR文件就是包含了META-INF/MANIFEST的ZIP文件,所以,
只需要使用WinZip、WinRAR等工具创建所需要ZIP压缩包,再往这个ZIP
压缩包中添加一个包含MANIFEST文件的META-INF目录即可。对于使用jar
命令的-m参数指定清单文件的情况,只需要将这个MANIFEST按需要修改即
可。
3)使用jar命令创建ZIP文件
有些Linux下提供了unzip命令,但没有zip命令,所以需要可以对
ZIP文件进行解压,即不能创建ZIP文件。如要创建一个ZIP文件,使用带
-M参数的jar命令即可,因为-M参数表示制作JAR包的时候不添加
MANIFEST清单,那么只需要在指定目标JAR文件的地方将.jar扩展名改
为.zip扩展名,创建的就是一个不折不扣的ZIP文件了,如将上一节的第3)
个例子略作改动:
t
特别声明:
1:资料来源于互联网,版权归属原作者
2:资料内容属于网络意见,与本账号立场无关
3:如有侵权,请告知,立即删除。
本文发布于:2023-03-19 20:59:34,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/1679230775314171.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:怎么打包文件.doc
本文 PDF 下载地址:怎么打包文件.pdf
留言与评论(共有 0 条评论) |