这一章节开始JVM的编写,首先解决第一个问题,就是如何运行java程序,回想最开始学java时,写一个Hello.java的程序,使用javac Hello.java将源代码转换为class文件,然后使用java Hello运行程序。JVM是只认class文件的,如果想让JVM运行该Hello程序,就要通过java命令。本节将介绍java命令的常用方法,并实现java命令的解析。最终通过命令参数,读取对应的class数据。本节的代码均在classpath包

java 命令简介

Java应用程序需要一个入口点,这个入口点就是我们熟知的main()方法。如果一个类包含public static void main(String[] args)方法,这个类就可以用来启动Java应用程序,我们把这个类叫作主类.这个类是需要我们用命令行手动向JVM提供的;

Java命令一般有以下几种形式:

1
2
3
4
java [-options] class [args]
java [-options] -jar jarfile [args]
javaw [-options] class [args]
javaw [-options] -jar jarfile [args]

可以向 java 命令传递三组参数:选项、主类名(或者JAR文件名)
和 main() 方法参数。选项由减号-开头。通常,第一个非选项参数给出主类的完全限定名(fully qualified class name)。但是如果用户提供了–jar选项,第一个非选项参数表示JAR文件名,java命令必须从这个JAR文件中寻找主类。javaw命令和java命令几乎一样,唯一的差别在于,javaw命令不显示命令行窗口,因此特别适合用于启动GUI(图形用户界面)应用程序。

可以看到命令中都带有一个 [-options]代表可选项, java命令行常用选项如下:

选项 用途
-version 输出版本信息
-?/-help 输出帮助信息
-cp/-classpath 指定用户类路径
-Dproperty=value 设置Java系统属性
-Xms<size> 设置初始堆空间大小
-Xmx<size> 设置最大堆空间大小
-Xss<size> 设置线程栈空间大小

当然,java命令行里提供的选项有很多,这里并不打算全部实现,只是挑几个比较重要的去实现.

接下来我打算实现以下几种命令:

  1. java -version
  2. java -?
  3. java -help
  4. java -cp your/classpath yourClassName arg1 arg2 ...
  5. java -classpath your/classpath yourClassName arg1 arg2 ...

命令行解析功能编码

定义Cmd类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class Cmd {
boolean isRightFmt = true; //是否是正确的格式;
boolean helpFlag; //是否是help 查看帮助
boolean versionFlag; //是否是查看版本
String cpOption = ""; //classPath 的路径; java -cp(-classpath) xxx
String clazz; //要编译的class 文件;
String[] args; //执行clazz文件需要的参数

public Cmd(String[] strs){
parseCmd(strs);
}

public void parseCmd(String[] args) {
int index = 1;

if (args.length<2){
isRightFmt = false;
return;
}
//首先判断开头是不是 java ,如果连这个都不是,直接退出吧,提示正确的使用方法;
if (!args[0].equals("java")) {
isRightFmt = false;
} else {
if (args[1].equals("-help") || args[1].equals("-?")) {
helpFlag = true;
} else if (args[1].equals("-version")) {
versionFlag = true;
} else if (args[1].equals("-cp") || args[1].equals("classpath")) {
if (args.length < 4) {
//如果走到这一步,那么命令行必定是java -cp aa/bb test 11 22 33 的形式,所以应该至少有4项;
isRightFmt = false;
}
index = 4;
this.cpOption = args[2];
} else if (args[1].equals("-Xjre")) {
if (args.length < 4) {
//如果走到这一步,那么命令行必定是java -Xjre "C:\Program Files\Java\jdk1.8.0_20\jre" java.lang.Object 的形式,所以应该至少有4项;
isRightFmt = false;
}
index = 4;
this.XjreOption = args[2];
}

this.clazz = args[index - 1];
this.args = new String[args.length - index];
for (int i = index; i < args.length; i++) {
this.args[i - index] = args[i];
}
}
}

//命令行格式错误,输出帮助信息
public void printUsage() {
System.out.println("Usage: java [-options] class [args...]\n");
}
}

这里定义了要解析的成员变量,注意这里包含了一个我们自己定义的-Xjre的选项,其具体含义见下一节,其它选项含义参见注释,主要功能在parseCmd()方法中实现解析参数,根据对应的位置的选项,然后在主函数中去使用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
Cmd cmd = new Cmd(args);

if (!cmd.isRightFmt) {
cmd.printUsage();
} else {
if (cmd.versionFlag) {
System.out.println("java version \"1.8.0_20\"\n"
+ "Java(TM) SE Runtime Environment (build 1.8.0_20-b26)\n"
+ "Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)");
} else if (cmd.helpFlag || cmd.args == null) {
cmd.printUsage();
} else {
startJVM(cmd);
}
}
}

首先检验命令行格式是否正确,如果不正确,输出使用信息,如果是查看版本,输出版本信息,这里暂时提供了一个临时的字符串,此功能待后续实现.如果是查看帮助信息,同样也使出使用信息.其它情况,传递给一个方法startJVM()实现真正的开启虚拟机的逻辑,当然,此方法现在暂时内部是空实现,待后续实现。