首先,我们来看一看当我们用命令执行javac和java的时候,系统做了些什么工作,假如我们现在有一个文件夹如下图1所示,有两个java文件。

图1:文件目录示意图

其中,Main.java的内容非常简单:

public class Main
{
         public static void main(String[] args)
         {
             System.out.println("hello world");
         }
}
public class Main
{
         public static void main(String[] args)
         {
             System.out.println("hello world");
         }
}

我们先在cmd下把目录切换到,上图1所示的目录G:\oj\workdir\kown\test下:

图2:切换目录

发现一个好玩的事情,在用户(home)目录下直接切换,是不行的,必须有改变盘符的命令才能切换,如图2,可以交换第一句和第二句的顺序。当然更加快捷的方式是在图1所示的文件夹下按F4,输入cmd然后回车。

我们来编译一下Main.java 文件输入javac Main.java

图3:javac Main

CMD是怎样执行javac Main.java的呢?首先是解析命令字符串,有空格分隔。把第一个参数作为可执行程序,后面的参数作为可执行程序的参数。就像我们写c,c++等程序的main函数int main(int argc, char *argv[]),argc是参数个数,argv[]是参数。Javac是用c语言编写的,肯定也有main函数,我们的外部程序就可以通过argv[]这个参数把外部参数传递给javac,同样CMD也是一个可执行程序,他也有这样的输入参数。

所以整个过程,就是我们在CMD下输入javac Main.java 就是把javac Main.java作为参数通过argc这样的参数传递给CMD,CMD解析参数字符串,知道要执行javac程序,CMD首先会查找当前目录下有没有javac这样的可执行程序,你可以尝试在当前文件夹下加入一个javac.com, javac.bat或者javac.exe这样的可执行文件,重新运行javac Main.java试一试,就知道CMD首先查找的是当前目录了。

如果找到则执行,如果没有找到就开始查找系统环境变量path指定的目录。这就是我们要把类似于C:\Program Files\Java\jdk7\bin这样的目录加入到系统环境变量path中的原因了。

现在我们来执行java命令,先看一看我们的目录情况,只是多了一个编译Main.java产生的字节码文件Main.class:

java smbfile 切换目录 java切换工作目录_java

图4:当前目录

执行java Main

图5:java Main

那么执行java和javac有什么相同和不同的地方呢?对于CMD过程都是一样,只是执行javac和java这两个程序本身不一样,javac只是编译java的源代码成为class的字节码。Java就是执行字节码。下面我们来看一看java的执行过程。

首先,通过path查找到java.exe,这是Windows下的可执行文件,在C:\Program Files\Java\jdk7\bin下可以找到。

java.exe会连接一个jvm.dll,dll文件是Windows的动态链接库,里面就是一些模块,函数,只不过他可以动态加载而已。而jvm.dll的作用有初始化一个JVM(java虚拟机),

然后、JVM初始化一个BootstrapLoader(启动类加载器)实例,Bootstrap Loader自动加载ExtendedLoader(标准扩展类加载器),并将其父Loader设为Bootstrap Loader。Bootstrap Loader自动加载AppClass Loader(系统类加载器),并将其父Loader设为Extended Loader。

有了类加载器,就要加载类了吧。首先是Bootstrap Loader加载,Bootstrap Loader加载类的路径写在有JRE环境变量中,JVM运行也被加在了系统环境变量中,可以通过System.getProperty("sun.boot.class.path")这样的方式得到。同理Extended Loader加载类的目录可以通过System.getProperty("java.ext.dirs")得到。AppClassLoader加载类的目录可以通过System.getProperty("java.class.path")得到。

看到AppClassLoader的java.class.path是不是有一点眼熟,这个就是java系统环境变量中的classpath。

运行一个class时,总是由AppClass Loader(系统类加载器)开始加载指定的类。但是每个类加载器会先将加载任务委托给其父加载器,如果其父找不到,再由自己去加载。

所以我们运行java Main的时候AppClassLoader会在BootstrapLoader指定的加载路径查找Main.class,然后在ExtendedLoader指定的路径查找Main.class,最后在AppClassLoader指定的classpath目录下查找。

值得注意的是当我们在当前目录下运行java Main能够加载到Main.class有两种可能的原因:

一是:AppClassLoader搜索了当前路径。

二是:JVM动态的把当前路径加入到了classpath中。

通过把当前文件夹下的OpenMain.class文件添加到classpath中,只是让他们的输出不同,测试后发现先执行的是classpath下的同名文件,我们知道应该是JVM动态的把当前路径添加到了classpath最后。

我们注意到Main.java是没有包名的,那么有包名的怎么办内?我们来看一看OpenMain.java

OpenMain.java的类容如下:

package kown.test;
public class OpenMain
{
         public static void main(String[] args)
         {
              System.out.println("hi world");
         }
}
package kown.test;
public class OpenMain
{
         public static void main(String[] args)
         {
              System.out.println("hi world");
         }
}

编译OpenMain.java:

图6:javac -encoding

     我们发现是编码不可映射错误,这是编码的问题,javac默认使用的系统环境变量默认的字符集(中文一般是GBK),因为上面的OpenMain.java文件是用utf-8编码。所以没有问题我们可以用:Javac –encoding utf-8 OpenMain.java来编译,也可以先把OpenMain.java另存为ANSI格式,再来编译,上面是采用改变了文件OpenMain.java的编码来处理。

运行:java OpenMain

java smbfile 切换目录 java切换工作目录_python_02

图7:wrong name

发现wrong name:/kown/test/OpenMain,这是怎么回事呢?我们先来看一看上面图7和下面图8的区别。

图8:无法加载主类

    图8这样的错误的原因是BootstrapLoader,ExtendedLoader和AppClassLoader加载的目录下根本就找不到OpenMain.class这个文件。而图7的错误原因是找到了OpenMain.class文件,但是没有找到OpenMain这个类。我们来分析一下图7中的错误,Exception in Thread main java.lang.NoClassDefFoundError:OpenMain······通过这个我们知道JVM已经启动了,并且已经加载了OpenMain.class字节码。但是给了我们一个wrong name :kown/test/OpenMain

我们知道kown/test是我们的包名,并且加了包名才产生了错误。

    重新思考一下,java中为什么要引入包的概念,其实和命名空间一样是为了区分不同模块同名的问题,在想一想我们为什么在不同的包中就可以使用相同的类名呢?那是因为我们在类的名字前面加上包名来作为类的名称,因为报名不同,所以我们的类的名字其实是不同的。这就是为什么在java中我们经常要使用全限名例如我们用Class.forName(java.lang.String)我们要使用全限类名java.lang.String的原因。

我们在看下面图9和图10这两张图再继续分析:

图9:OpenMain.class所在的目录下运行

图10:包名的父目录下运行

情况一:图9:OpenMain.class所在的目录G:\oj\workdir\kown\test下分别运行了java OpenMain和java kown/test/OpenMain,一个是带上包名的,一个是没有带包名的。当我们不加包名的时候就告诉我们类名错误,当我加上包名的时候就告诉我们找不到类。

情况二:图10:包名的父目录G:\oj\workdir下分别运行了java OpenMain和java kown/test/OpenMain,也是一个是带上包名的,一个是没有带包名的,当我们不加包名的时候告诉我们找不到类,加了包名终于运行成功了。

为什么这么神奇,让我们来分析总结一下:

结合情况一和情况二,我们可以大胆的猜测java首先是通过他的参数来找class文件的,在情况一运行java OpenMain,因为当前目录有OpenMain.class文件,所以加载class文件阶段没有问题,然后加载类,java把参数OpenMain作为类名,用类似于Class.forName("OpenMain");这样的方式来加载OpenMain类,所以得到了java.lang.NoClassDefFoundError:OpenMain还有各个类加载器的Unknown Source错误,因为根本没有OpenMain这个类,而是kown.test.OpenMain这个类。

对于情况二,直接java OpenMain得到的错误其实应该是找不到主类,因为G:\oj\workdir根本没有这个类,之所以会出现像上面的情况,是因为我为了测试AppClassLoader搜索了当前路径,还是JVM动态的把当前路径加入到了classpath中。把OpenMain打了jar包加入了C:\Program Files\Java\jre7\lib\ext文件夹下。一定是jar包,而不是class文件才有效。

    所以java会按参数指定的路径下来搜索class文件,然后加载class文件到内存,然后把路径加上文件名作为全限类名来加载类。