2017-04-05 19:22:28 +0000   |     java compiler   |   Viewed times   |    

[编译] & [运行] 一个Java程序

分两步走,

  1. 先把.java源代码,编译成.class字节码。
  2. 执行.class字节码。

javac 编译

[javac] : 把源代码.java文件编译成字节码组成的.class文件。

javac命令的具体语法如下:

javac [ options ] [ sourcefiles ] [ @argfiles ]

java 执行

[java] : 执行编译好的.class字节码文件。

java命令的具体语法如下:

java [ options ] class [ arguments ]

虚拟机怎么找到.java源文件以及.class字节码文件?

编译和执行最关键的一点就是:虚拟机怎么找到.java源文件以及.class字节码文件?

假设现在有一个A类,它在com.ciaoshen.test包下。

package com.ciaoshen.test;

public class A {
    public void foo() {
        // do somthing
    }
    public static void main(String[] args) {
        A a = new A();
        a.foo();
    }
}

假设项目的根目录.的文件拓扑如下所示。A.java源文件放在src文件夹下。并且希望编译好的字节码A.class放在bin文件夹下。

.
├── bin     // .class字节码文件
│   └── com
│       └── ciaoshen
│           └── test
│               └── A.class
|
├── src     // .java源代码
|   └── com
|       └── ciaoshen
|           └── test
|               └── A.java
|
└── run.sh  // shell命令行脚本

最简单的shell命令行如下,

javac -d bin src/com/ciaoshen/test/A.java

java -cp bin com.ciaoshen.test.A

要让编译器能找到A.java源代码文件,必须给出A.java文件的路径(相对或绝对路径都可以)src/com/ciaoshen/test/A.java

这里的option -d, 是告诉编译器,编译好的.class字节码文件统一存放在bin文件夹下。否则编译器默认将.java源代码和.class字节码放在同一个文件夹src/com/ciaoshen/test/里。

重点来了:Class Path

java命令想要执行A类的main方法,必须告诉虚拟机到哪里去找编译好的A.class字节码文件。这就需要非常关键的 Class Path

设置Class Path,首选-cp(或者是-classpath)option。而不是设置$CLASSPATH环境变量。Oracle官方手册的原文如下:

The -classpath option is the recommended option for changing class path settings, because each application can have the class path it needs without interfering with any other application.The java command also has a -cp option that is an abbreviation for -classpath.

java-cpoption告诉编译器,bin文件夹是Class Path-cp命令将会覆盖系统的CLASSPATH环境变量。官方手册原文如下:

Specifying -classpath or -cp overrides any setting of the CLASSPATH environment variable.

如果既没有设置-cp,也没有用$CLASSPATH环境变量,默认当前目录.Class Path。原文如下:

If -classpath and -cp are not used and CLASSPATH is not set, then the user class path consists of the current directory (.).

Linux要设置系统$CLASSPATH可以用export命令。多个$CLASSPATHLinux下可以用:分割。下面命令行表示在原先$CLASSPATH集合里新加入...

export CLASSPATH=.:..:$CLASSPATH

但是用export命令,一旦终端关闭,就清除了。如果想要每次打开终端都自动设置$CLASSPATHLinux上可以配置~/.bashrc文件。Mac上可以配置~/.bash_profile文件。把上面这段命令写入文件即可。

编译多个源文件: -classpath & -sourcepath 配合使用

当某项任务需要有多个类协同工作的时候,需要在用javac编译的时候,同时编译多个.java源文件。现在假设需要执行A类的main方法。但A类的foo()函数依赖B类的帮助。

package com.ciaoshen.test;

public class A {
    public void foo() {
        B b = new B();
        b.help();
    }
    public static void main(String[] args) {
        A a = new A();
        a.foo();
    }
}
package com.ciaoshen.test;

public class B {
    public void help() {
        System.out.println("Hello Ronald!");
    }
}

假设A类和B类同在com.ciaoshen.test包下。并且com.ciaoshen.test包的文件拓扑如下所示。我们的命令行脚本文件run.sh直接处于项目根目录.下。

.
├── bin     // .class字节码文件
│   └── com
│       └── ciaoshen
│           └── test
│               ├── A.class
│               └── B.class
|
├── src     // .java源代码
|   └── com
|       └── ciaoshen
|           └── test
|               ├── A.java
|               └── B.java
|
└── run.sh  // shell命令行脚本

根据前面介绍的javac命令的语法,有两种最简单的做法。

第一种,直接给出A类和B类的.java源代码文件的路径。

javac -d bin src/com/ciaoshen/test/A.java src/com/ciaoshen/test/B.java

java -cp bin com.ciaoshen.test.A

这样,编译器就知道需要同时编译A.javaB.java两个源文件。而且两个文件可以交换顺序,不要求被依赖的B.javaA.java的前面。其他选项照旧。

第二种,先编译B.java。然后在编译A.java的时候,带上-cp选项,告诉编译器去哪里找B.class

javac -d bin src/com/ciaoshen/test/B.java
javac -d bin -cp bin src/com/ciaoshen/test/A.java

java -cp bin com.ciaoshen.test.A

第三种,利用javac@argfiles选项,把A.javaB.java预先写到一个文本文件。

javac -d bin @files.txt

java -cp bin com.ciaoshen.test.A

files.txt里,用空行或者空格将多个.java文件隔开。顺序不重要。

src/com/ciaoshen/test/A.java

src/com/ciaoshen/test/B.java

第四种,用javac-sourcepath选项。

-sourcepath告诉编译器到哪里去找.java源文件。

javac -d bin -sourcepath src src/com/ciaoshen/test/A.java

java -cp bin com.ciaoshen.test.A

但注意,不是说用了-sourcepath选项,就不需要指定需要编译的目标源文件了,相反我们还是必须至少指定一个编译的目标源文件。也就是说,上面命令行中的-sourcepath选项的潜台词其实是:

编译A.java文件,如果遇到某些依赖的类(比如说B类),就到-sourcepath指定的src文件夹下面去找。

第五种,同时使用javac-cp选项和-sourcepath选项。

这种做法是最稳妥的。 推荐使用。

javac -d bin -cp bin -sourcepath src src/com/ciaoshen/test/A.java

java -cp bin com.ciaoshen.test.A

上面的代码翻译成大白话就是:

编译A.java文件,如果遇到某些依赖的类(比如说B类),先到到-sourcepath指定的src文件夹下面找源文件B.java。如果找到了,就把B.java编译成B.class,如果bin文件夹下已经有B.class就更新成最新版。如果没有找到B.java,就到bin文件夹找有没有编译好的B.class,有就直接用,没有就报错。

也就是说有了-sourcepath选项,如果某些被依赖的源码有了改动,相应的比较旧的.class字节码文件会被自动更新。这就是为什么我更喜欢加上-sourcepath,这样不会因为改动了某些代码,但编译的时候旧版.class文件没有被更新导致的很难排查的bug。下面是官方手册关于-sourcepath特性的阐述,

Specify the source code path to search for class or interface definitions. Note: Classes found through the classpath are subject to automatic recompilation if their sources are found.

如果没有指定-sourcepath,系统默认到Class Path下面找.java源文件。一般项目.java源码和.class字节码肯定分开放,就会导致找不到.java文件。

If the -sourcepath option is not specified, the user class path is searched for both source files and class files.

附录

附上Oracle官方手册关于javac,java的文档原文,方便查阅。

javac

javac - Java programming language compiler

SYNOPSIS

 javac [ options ] [ sourcefiles ] [ @argfiles ]
Arguments may be in any order.
options
Command-line options.
sourcefiles
One or more source files to be compiled (such as MyClass.java).
@argfiles
One or more files that lists options and source files. The -J options are not allowed in these files.
DESCRIPTION

The javac tool reads class and interface definitions, written in the Java programming language, and compiles them into bytecode class files.
There are two ways to pass source code file names to javac:

For a small number of source files, simply list the file names on the command line.
For a large number of source files, list the file names in a file, separated by blanks or line breaks. Then use the list file name on the javac command line, preceded by an @ character.
Source code file names must have .java suffixes, class file names must have .class suffixes, and both source and class files must have root names that identify the class. For example, a class called MyClass would be written in a source file called MyClass.java and compiled into a bytecode class file called MyClass.class.
Inner class definitions produce additional class files. These class files have names combining the inner and outer class names, such as MyClass$MyInnerClass.class.

You should arrange source files in a directory tree that reflects their package tree. For example, if you keep all your source files in \workspace, the source code for com.mysoft.mypack.MyClass should be in \workspace\com\mysoft\mypack\MyClass.java.

By default, the compiler puts each class file in the same directory as its source file. You can specify a separate destination directory with -d (see Options, below).

SEARCHING FOR TYPES

When compiling a source file, the compiler often needs information about a type whose definition did not appear in the source files given on the command line. The compiler needs type information for every class or interface used, extended, or implemented in the source file. This includes classes and interfaces not explicitly mentioned in the source file but which provide information through inheritance.
For example, when you subclass java.applet.Applet, you are also using Applet's ancestor classes: java.awt.Panel, java.awt.Container, java.awt.Component, and java.lang.Object.

When the compiler needs type information, it looks for a source file or class file which defines the type. The compiler searches for class files first in the bootstrap and extension classes, then in the user class path (which by default is the current directory). The user class path is defined by setting the CLASSPATH environment variable or by using the -classpath command line option. (For details, see Setting the Class Path).

If you set the -sourcepath option, the compiler searches the indicated path for source files; otherwise the compiler searches the user class path for both class files and source files.

You can specify different bootstrap or extension classes with the -bootclasspath and -extdirs options; see Cross-Compilation Options below.

A successful type search may produce a class file, a source file, or both. Here is how javac handles each situation:

Search produces a class file but no source file: javac uses the class file.
Search produces a source file but no class file: javac compiles the source file and uses the resulting class file.
Search produces both a source file and a class file: javac determines whether the class file is out of date. If the class file is out of date, javac recompiles the source file and uses the updated class file. Otherwise, javac just uses the class file.
javac considers a class file out of date only if it is older than the source file.

Note:   javac can silently compile source files not mentioned on the command line. Use the -verbose option to trace automatic compilation.
OPTIONS

The compiler has a set of standard options that are supported on the current development environment and will be supported in future releases. An additional set of non-standard options are specific to the current virtual machine and compiler implementations and are subject to change in the future. Non-standard options begin with -X.
Standard Options

-classpath classpath
Set the user class path, overriding the user class path in the CLASSPATH environment variable. If neither CLASSPATH or -classpath is specified, the user class path consists of the current directory. See Setting the Class Path for more details.
If the -sourcepath option is not specified, the user class path is searched for both source files and class files.

-Djava.ext.dirs=directories
Override the location of installed extensions.
-Djava.endorsed.dirs=directories
Override the location of endorsed standards path.
-d directory
Set the destination directory for class files. The destination directory must already exist; javac will not create the destination directory. If a class is part of a package, javac puts the class file in a subdirectory reflecting the package name, creating directories as needed. For example, if you specify -d c:\myclasses and the class is called com.mypackage.MyClass, then the class file is called c:\myclasses\com\mypackage\MyClass.class.
If -d is not specified, javac puts the class file in the same directory as the source file.

Note:   The directory specified by -d is not automatically added to your user class path.

-deprecation
Show a description of each use or override of a deprecated member or class. Without -deprecation, javac shows the names of source files that use or override deprecated members or classes. -deprecation is shorthand for -Xlint:deprecation.
-encoding encoding
Set the source file encoding name, such as EUC-JP and UTF-8. If -encoding is not specified, the platform default converter is used.
-g
Generate all debugging information, including local variables. By default, only line number and source file information is generated.
-g:none
Do not generate any debugging information.
-g:{keyword list}
Generate only some kinds of debugging information, specified by a comma separated list of keywords. Valid keywords are:
source
Source file debugging information
lines
Line number debugging information
vars
Local variable debugging information
-help
Print a synopsis of standard options.
-nowarn
Disable warning messages. This has the same meaning as -Xlint:none.

-source release
Specifies the version of source code accepted. The following values for release are allowed:
1.3	the compiler does not support assertions, generics, or other language features introduced after JDK 1.3.
1.4	the compiler accepts code containing assertions, which were introduced in JDK 1.4.
1.5	the compiler accepts code containing generics and other language features introduced in JDK 5. The compiler defaults to the version 5 behavior if the -source flag is not used.
5	Synonym for 1.5

-sourcepath sourcepath
Specify the source code path to search for class or interface definitions. As with the user class path, source path entries are separated by semicolons (;) and can be directories, JAR archives, or ZIP archives. If packages are used, the local path name within the directory or archive must reflect the package name.
Note:   Classes found through the classpath are subject to automatic recompilation if their sources are found.

-verbose
Verbose output. This includes information about each class loaded and each source file compiled.
-X
Display information about non-standard options and exit.

java

Synopsis
java [ options ] class [ arguments ]

java [ options ] -jar file.jar [ arguments ]

javaw [ options ] class [ arguments ]

javaw [ options ] -jar file.jar [ arguments ]

options
Command-line options. See Options.

class
The name of the class to be called.

file.jar
The name of the JAR file to be called. Used only with the -jar command.

arguments
The arguments passed to the main function.

Description
The java command starts a Java application. It does this by starting a Java runtime environment, loading a specified class, and calling that class's main method.

The method must be declared public and static, it must not return any value, and it must accept a String array as a parameter. The method declaration has the following form:

public static void main(String[] args)
By default, the first argument without an option is the name of the class to be called. A fully qualified class name should be used. If the -jar option is specified, then the first non-option argument is the name of a JAR file containing class and resource files for the application, with the startup class indicated by the Main-Class manifest header.

The Java runtime searches for the startup class, and other classes used, in three sets of locations: the bootstrap class path, the installed extensions, and the user class path.

Non-option arguments after the class name or JAR file name are passed to the main function.

The javaw command is identical to java, except that with javaw there is no associated console window. Use javaw when you do not want a command prompt window to appear. The javaw launcher will, however, display a dialog box with error information if a launch fails for some reason.

Options
The launcher has a set of standard options that are supported in the current runtime environment.

In addition, the default Java HotSpot VMs provide a set of non-standard options that are subject to change in future releases. See Nonstandard Options.

Standard Options
-client
Selects the Java HotSpot Client VM. A 64-bit capable JDK currently ignores this option and instead uses the Java Hotspot Server VM.

For default Java VM selection, see the Server-Class Machine Detection page at
http://docs.oracle.com/javase/7/docs/technotes/guides/vm/server-class.html

-server
Selects the Java HotSpot Server VM. On a 64-bit capable JDK, only the Java Hotspot Server VM is supported so the -server option is implicit.

For default a Java VM selection, see the Server-Class Machine Detection page at
http://docs.oracle.com/javase/7/docs/technotes/guides/vm/server-class.html

-agentlib:libname[=options]
Loads native agent library libname, for example:

-agentlib:hprof

-agentlib:jdwp=help

-agentlib:hprof=help
See JVMTI Agent Command-Line Options at
http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html#starting

-agentpath:pathname[=options]
Loads a native agent library by full pathname.

-classpath classpath
-cp classpath
Specifies a list of directories, JAR files, and ZIP archives to search for class files. Separate class path entries with semicolons (;). Specifying -classpath or -cp overrides any setting of the CLASSPATH environment variable.

If -classpath and -cp are not used and CLASSPATH is not set, then the user class path consists of the current directory (.).

As a special convenience, a class path element that contains a base name of * is considered equivalent to specifying a list of all the files in the directory with the extension .jar or .JAR. A Java program cannot tell the difference between the two invocations.

For example, if directory mydir contains a.jar and b.JAR, then the class path element mydir/* is expanded to a A.jar:b.JAR, except that the order of jar files is unspecified. All jar files in the specified directory, even hidden ones, are included in the list. A class path entry consisting simply of * expands to a list of all the jar files in the current directory. The CLASSPATH environment variable, where defined, will be similarly expanded. Any class path wildcard expansion occurs before the Java VM is started. No Java program will ever see wild cards that are not expanded except by querying the environment. For example, by calling System.getenv("CLASSPATH").

-Dproperty=value
Sets a system property value.

If value is a string that contains spaces, then you must enclose the string in double quotation marks:

java -Dmydir="some string" SomeClass
-disableassertions[:package name"..." | :class name ]
-da[:package name"..." | :class name ]
Disable assertions. This is the default.

With no arguments, -disableassertions or -da disables assertions. With one argument ending in "...", the switch disables assertions in the specified package and any subpackages. If the argument is "...", then the switch disables assertions in the unnamed package in the current working directory. With one argument not ending in "...", the switch disables assertions in the specified class.

To run a program with assertions enabled in package com.wombat.fruitbat but disabled in class com.wombat.fruitbat.Brickbat, the following command could be used:

java -ea:com.wombat.fruitbat... -da:com.wombat.fruitbat.Brickbat <Main Class>
The -disableassertions and -da switches apply to all class loaders and to system classes (which do not have a class loader). There is one exception to this rule: in their no-argument form, the switches do not apply to system. This makes it easy to turn on asserts in all classes except for system classes. The -disablesystemassertions option provides a separate swith to enable assertions in all system classes.

-enableassertions[:package name"..." | :class name ]
-ea[:package name"..." | :class name ]
Enable assertions. Assertions are disabled by default.

With no arguments, -enableassertions or -ea enables assertions. With one argument ending in "...", the switch enables assertions in the specified package and any subpackages. If the argument is "...", then the switch enables assertions in the unnamed package in the current working directory. With one argument not ending in "...", the switch enables assertions in the specified class.

If a single command contains multiple instances of these switches, then they are processed in order before loading any classes. So, for example, to run a program with assertions enabled only in package com.wombat.fruitbat (and any subpackages), the following command could be used:

java -ea:com.wombat.fruitbat... <Main Class>
The -enableassertions and -ea switches apply to all class loaders and to system classes (which do not have a class loader). There is one exception to this rule: in their no-argument form, the switches do not apply to system. This makes it easy to turn on asserts in all classes except for system classes. The -enablesystemassertions option provides a separate switch to enable assertions in all system classes.

-enablesystemassertions
-esa
Enable assertions in all system classes (sets the default assertion status for system classes to true).

-disablesystemassertions
-dsa
Disables assertions in all system classes.

-help or -?
Displays usage information and exit.

-jar
Executes a program encapsulated in a JAR file. The first argument is the name of a JAR file instead of a startup class name. For this option to work, the manifest of the JAR file must contain a line in the form Main-Class: classname. Here, classname identifies the class with the public static void main(String[] args) method that serves as your application's starting point.

When you use this option, the JAR file is the source of all user classes, and other user class path settings are ignored.

-javaagent:jarpath[=options]
Loads a Java programming language agent. For more information about instrumenting Java applications, see the java.lang.instrument package description in the Java API documentation at http://docs.oracle.com/javase/7/docs/api/java/lang/instrument/package-summary.html

-jre-restrict-search
Includes user-private JREs in the version search.

-no-jre-restrict-search
Excludes user-private JREs in the version search.

-showversion
Displays version information and continue. (See also -version.)

-splash:imagepath
Shows splash screen with image specified by imagepath.

-verbose
-verbose:class
Displays information about each class loaded.

-verbose:gc
Reports on each garbage collection event.

-verbose:jni
Reports information about use of native methods and other Java Native Interface activity.

-version
Displays version information and exit. See also the -showversion option.

-version:release
Specifies that the version specified by the release is required by the class or JAR file specified on the command line. If the version of the java command called does not meet this specification and an appropriate implementation is found on the system, then the appropriate implementation will be used.

The release option specifies an exact version and a list of versions called a version string. A version string is an ordered list of version ranges separated by spaces. A version range is either a version-id, a version-id followed by an asterisk (*), a version-id followed by a plus sign (+), or a version range that consists of two version-ids combined using an ampersand (&). The asterisk means prefix match, the plus sign means this version or greater, and the ampersand means the logical and of the two version-ranges, for example:

-version:"1.6.0_13 1.6*&1.6.0_10+"
The meaning of the previous example is that the class or JAR file requires either version 1.6.0_13, or a version with 1.6 as a version-id prefix and that is not less than 1.6.0_10. The exact syntax and definition of version strings can be found in Appendix A of the Java Network Launching Protocol & API Specification (JSR-56).

For JAR files, the preference is to specify version requirements in the JAR file manifest rather than on the command line.

See Notes for important policy information on the use of this option.

参考文献

  1. javac命令 - Java programming language compiler -> http://docs.oracle.com/javase/1.5.0/docs/tooldocs/windows/javac.html

  2. java命令 - Oracle Help Center -> http://docs.oracle.com/javase/7/docs/technotes/tools/windows/java.html

  3. PATH & CLASSPATH - Java Tutorials -> https://docs.oracle.com/javase/tutorial/essential/environment/paths.html

  4. Setting the Class Path -> https://docs.oracle.com/javase/8/docs/technotes/tools/windows/classpath.html#BEHJBHCD

  5. Using CLASSPATH and SOURCEPATH -> http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_CLASSPATH_and_SOURCEPATH.html