- 浏览: 2871181 次
- 性别:
- 来自: 武汉
文章分类
- 全部博客 (1173)
- 名言警句 (5)
- 心情随笔 (50)
- 数据库 (57)
- Java基础 (241)
- J2EE框架 (91)
- 数据结构 (12)
- 程序设计 (21)
- WEB技术 (128)
- 网络日志 (12)
- IT资讯 (247)
- linux (64)
- solaris (2)
- 其它 (143)
- WebService (4)
- 日语学习 (2)
- 机器人 (5)
- Android (5)
- cgywin (3)
- Game (1)
- DWR (1)
- spring (8)
- canvas (1)
- Guava (3)
- Modbus (5)
- 测试 (6)
- mongodb (9)
- Quartz (2)
- Cron (1)
- windows (2)
- 持续集成 (1)
- bootstrap (3)
- 结对编程 (1)
- nodejs (1)
- Netty (1)
- 安全 (3)
- webstorm (2)
- sparkline (1)
- Job (1)
- git (3)
- Maven (3)
- knockout (5)
- jquery (1)
- bower (1)
- docker (1)
- confluence (4)
- wiki (1)
- GoogleMap (1)
- jekyll (10)
- ruby (2)
- npm (3)
- browserify (1)
- gulp (3)
- openwrt (1)
- discuz (3)
- 输入法 (1)
- JPA (1)
- eclipse (2)
- IntelliJ (1)
- css (1)
- 虚拟机 (1)
- 操作系统 (1)
- azkaban (2)
- scrum (1)
最新评论
-
pangxiea_:
你好, 想请问一下 Linux下 这么使用rxtxcomm 在 ...
使用Java进行串口通信 -
abababudei:
请教一下,这个您是怎么解决的:/dev/ttyS2enteri ...
Java应用程序的MODBUS通讯 -
xuniverse:
hannibal005 写道楼主,我问下 request.se ...
用javascript与java进行RSA加密与解密 -
atxkm:
找了一下午,终于找到了
gulp 拷贝文件时如何移除文件目录结构 -
kalogen:
gtczr 写道非常感谢,经过我自己的修改,已经完美实现。发出 ...
用javascript与java进行RSA加密与解密
众所周知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=<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文件中。
----_JAVA_LAUNCHER_DEBUG----
一个执行的外壳,它会装载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=<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文件中。
----_JAVA_LAUNCHER_DEBUG----
发表评论
-
开源中国的 IT 公司开源软件整理计划介绍
2015-04-27 09:19 835为了方便大家检索开源软件,促进开源在中国的进一步发展,开源中 ... -
40 个超棒的免费 Bootstrap HTML5 网站模板
2015-04-11 16:12 2834Bootstrap 是快速开发Web应用程序的前端工具包。它 ... -
前端与后端的测试工具组合
2015-01-15 13:03 2184在Java领域,Apache, Spring, JBoss ... -
离开自己的安乐窝
2015-01-03 13:30 655以下是我对世界的看法: 在一个你熟悉的地方,舒 ... -
废水变清洁能源:新型太阳能装置净水产氢
2013-10-18 09:21 826加州大学的科研团队将太阳能电池与微生物燃料电池巧妙的整合在一 ... -
Nagios 4.0 正式版发布,企业级监控系统
2013-09-25 08:50 979Nagios是一个监视系统运行状态和网络信息的监视系统。Na ... -
如何管理一个远程团队
2013-07-04 06:50 1307过去的几个月里耗尽了大量的精力一直在争论 ... -
2012 年最佳 Android 应用
2013-01-09 18:35 9822012 年最佳 Android 应用 • ... -
我跳槽是因为他们的显示器更大
2012-05-29 22:39 1473好的技术人员向往具有很强的企业技术文化氛围的工作场所。但 ... -
听命于我!让机器人流上道德的血液
2012-04-01 11:47 1473随着机器人变得越来 ... -
我国科学家发现新中微子振荡 有助研究反物质
2012-03-09 18:49 1212大亚湾中微子实验国际合作组发言人王贻芳在北京宣布,大亚湾中 ... -
IBM 新专利: 通过代码提交评判程序员
2012-03-06 12:34 1159觉得老板只需要用软 ... -
程序员水平分级 你属于哪一类?
2012-02-15 22:32 1093近日,whattofix.com 刊登了一篇 Danie ... -
为程序员量身定制的12个目标
2012-01-15 23:12 892对程序员们来说挑战自我非常重要,要么不断创新,要么技术停 ... -
Android之父Andy Rubin访谈录
2011-10-20 22:44 1157就在昨天的Ice Cream Sandwich发布会结束以 ... -
Firebug 1.8 功能
2011-09-12 08:40 1115著名的Firefox网页调试开发插件 Firebug 1.8 ... -
湖南长沙真人《愤怒的小鸟》主题公园
2011-09-04 08:38 1097好吧,首先是山寨暴雪主题公园,现在又有现实版的《愤怒的小鸟 ... -
中国第一代程序员列传
2011-08-12 22:36 12011年7月,仅仅47岁的“中国第一程序员”求伯君彻底退 ... -
美国IT圈家谱
2011-07-21 21:52 1390为什么全球的科技新闻/互联网新闻/创业新闻都被美国所占据? ... -
DONA,可爱的智能机器人乞丐
2011-04-17 21:17 1080大家一定见惯了地铁中的乞讨人,不过都已经审美疲劳了 ...
相关推荐
下面将详细解析JVM对.dll文件的装载过程以及对Java类的装载过程。 首先,了解JVM对.dll文件的装载过程,这包括几个关键步骤: 1. 创建JVM装载环境和配置:JVM启动时首先会创建一个运行环境,这包括分配内存、设置...
标题中的“JVM崩溃”指的是Java虚拟机(Java Virtual Machine)在运行过程中遇到了无法处理的错误,导致程序异常终止的现象。这通常是由于内存溢出、类装载错误、线程死锁或其他严重问题引起的。理解JVM崩溃的原因和...
标题中的"jvm.rar_test 2410"暗示了我们正在处理与Java虚拟机(JVM)相关的文件,可能是某个版本或补丁的编号。..."jvm.dll"可能是Windows环境下JVM的一个动态链接库,用于实现某些特定功能或优化。
JVM的启动过程涉及查找JRE路径,通常是通过JDK中的Java.exe来完成,它会检查指定路径下的Java.dll文件,以确定JRE的位置。一旦JVM环境创建成功,它就会管理Java程序的生命周期,包括内存分配、垃圾回收、线程调度等...
例如,在Windows平台,JVM会搜索`libname.dll`,而在Linux平台则会搜索`libname.so`。 - **示例**:`-agentlib:hprof`。 - **-agentpath:pathname[=options]**:与-agentlib类似,但按全路径装载本地库,不再...
1. **装载jvm动态库**:在Windows平台下,动态库文件名为`jvm.dll`,通常位于`%JAVA_HOME%/jre/bin/client/`和`%JAVA_HOME%/jre/bin/server/`目录下。 2. **查找JNI_CreateJavaVM接口**:这是启动Java虚拟机的核心...
Java代理是通过JVM的 `-javaagent` 启动参数指定的,它允许在类装载时或之后插入自定义的行为。这通常用于动态字节码操作,例如代码覆盖率报告、性能监控、事务管理等。当JVM无法找到指定的代理时,可能的原因有: ...
JNI的工作原理类似于代理模式,其中JVM充当了Java代码与本地代码之间的桥梁。具体步骤如下: 1. **定义本地方法**:在Java代码中,通过`native`关键字声明本地方法。 2. **生成JNI头文件**:使用`javah`工具根据...
3. 装载动态库:在运行时,FFI需要能够加载和查找外部函数所在的动态链接库(DLL,SO,dylib等)。这通常通过系统级别的函数如dlopen(Linux/Unix)、LoadLibrary(Windows)来实现。 4. 调用外部函数:找到函数后...
图片到图片装载器、绘制火焰效果的X坐标,Y坐标、得到X坐标,Y坐标值、绘制火焰效果Image…… Java加密解密工具集 JCT v1.0源码包 5个目标文件 内容索引:JAVA源码,综合应用,JCT,加密解密 WDSsoft的一款免费源代码 JCT ...