摘要:那本文为什么说,可以不编译直接执行了呢其实,这个是里新加的一个,目的是使单个文件的源码可以无需编译,直接执行。中还提到,在类操作系统下,上面的代码还可以以形式执行。我们再写一个例子看下看到没,我们用写的代码居然可以像脚本一样直接执行了。
我们都知道java是静态语言,也就是说,如果你想执行java程序,就必须先编译,再执行。
那本文为什么说,java可以不编译直接执行了呢?
其实,这个是OpenJDK11里新加的一个feature,目的是使单个文件的java源码可以无需编译,直接执行。
下面的JEP里对该特性做了详细的描述:
http://openjdk.java.net/jeps/330
我们先写个小例子实验下:
$ cat Test.java public class Test { public static void main(String[] args) { System.out.println("hello"); } } $ java Test.java hello
真的可以执行,神奇。
JEP 330 中还提到,在类Unix操作系统下,上面的代码还可以以 "Shebang" 形式执行。
我们再写一个例子看下:
$ cat Test #!/usr/bin/java --source 12 public class Test { public static void main(String[] args) { System.out.println("hello"); } } $ chmod +x Test $ ./Test hello
看到没,我们用java写的代码居然可以像shell脚本一样直接执行了。
那这一切在JVM中又是怎么实现的呢?静态语言为什么也可以像脚本一样动态执行了呢?
下面我们来看下对应的JVM源码:
// src/java.base/share/native/libjli/java.c static jboolean ParseArguments(int *pargc, char ***pargv, int *pmode, char **pwhat, int *pret, const char *jrepath) { ... if (mode == LM_SOURCE) { ... *pwhat = SOURCE_LAUNCHER_MAIN_ENTRY; ... } ... *pmode = mode; return JNI_TRUE; }
当我们要执行的java程序是java源文件时,该方法中的mode就会被设置为LM_SOURCE。
pwhat指针指向的是我们最终要执行的带main方法的java类,由上我们可以看到,在mode为LM_SOURCE时,最终执行的java类并不是我们提供的java源文件对应的java类,而是SOURCE_LAUNCHER_MAIN_ENTRY宏定义的java类。
我们看下这个宏对应的java类是什么:
// src/java.base/share/native/libjli/java.c #define SOURCE_LAUNCHER_MAIN_ENTRY "jdk.compiler/com.sun.tools.javac.launcher.Main"
由上可见,它是jdk.compiler模块里的一个类,java命令最终执行的main方法就是这个类里的main方法。
那这个main方法的参数是什么呢?
其实就是我们提供的java源文件,不过为了更加明确,我们还是通过以下方式验证下:
$ _JAVA_LAUNCHER_DEBUG=1 java Test.java ----_JAVA_LAUNCHER_DEBUG---- # 省略无关信息 Source is "jdk.compiler/com.sun.tools.javac.launcher.Main" App"s argc is 1 argv[ 0] = "Test.java" # 省略无关信息 ----_JAVA_LAUNCHER_DEBUG---- hello
如果我们在启动java之前,设置了_JAVA_LAUNCHER_DEBUG环境变量,JVM内部就会输出一些运行时的数据来供我们调试,比如,由上面的输出我们可以看到,java命令将要执行的带main方法的java类为jdk.compiler/com.sun.tools.javac.launcher.Main,其参数为Test.java,正好和我们上文中分析的是一样的。
也就是说,当我们以源文件形式执行java命令时,最终调用的main方法是jdk.compiler/com.sun.tools.javac.launcher.Main里的main方法,其参数为我们要执行的java源文件。
下面我们再来看下这个main方法究竟是如何执行我们的源文件的:
// com.sun.tools.javac.launcher.Main public class Main { ... public static void main(String... args) throws Throwable { try { new Main(System.err).run(VM.getRuntimeArguments(), args); } catch (Fault f) { ... } } ... public void run(String[] runtimeArgs, String[] args) throws Fault, InvocationTargetException { Path file = getFile(args); // 我们要执行的源文件 ... String mainClassName = compile(file, getJavacOpts(runtimeArgs), context); String[] appArgs = Arrays.copyOfRange(args, 1, args.length); execute(mainClassName, appArgs, context); } ... private void execute(String mainClassName, String[] appArgs, Context context) throws Fault, InvocationTargetException { ... try { Class> appClass = Class.forName(mainClassName, true, cl); Method main = appClass.getDeclaredMethod("main", String[].class); ... main.invoke(0, (Object) appArgs); } catch (ClassNotFoundException e) { ... } } }
在这里我们只列出了相关方法的大致逻辑,不过已经足够能看出,它到底是怎么执行的了。
我们要执行的源码先被java的compiler编译,然后又调用了其main方法继续执行我们写的逻辑。
原来是如此简单。
不过,java源码可动态执行的特性还是给我们留下了很多想像空间,虽然其实现机制很粗暴,但对用户来说还算是友好的。
希望本篇文章能给各位同学带来一些收获。
完。
更多原创文章,请关注我微信公众号:
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/76052.html
摘要:比如说,就是复姓,名字为的类别则是复姓,名字为的类别。先介绍的机制基本原则需要将类文件切实安置到其所归属之所对应的相对路径下。把源代码文件,文件和其他文件有条理的进行一个组织,以供来使用。可以使用通配符,代表某下所有的,不包括子目录。 一些人用了一阵子的Java,可是对于 Java 的 package 跟 import 还是不太了解。很多人以为原始码 .java 文件中的 import...
摘要:需要注意的地方输入法状态调整为英文状态代码的缩进不要忘记分号下面图片标注内容。语句语句是程序最小的一个执行单位,像一个指令,程序中,必须使用一个英文分号结束一条语句。建议,第一个简单的程序,我已经详细的为你做了演练与解释。 在上一篇文章 【[准备编译环境】]()中我们完成了 Java 编译环境的搭建,这篇文章内容主要是来教你怎么开始编写第一个 Java 程序,并运行它。 分为两个步骤,...
摘要:而字节码运行在之上,所以不用关心字节码是在哪个操作系统编译的,只要符合规范,那么,这个字节码文件就是可运行的。好处防止内存中出现多份同样的字节码安全性角度特别说明类加载器在成功加载某个类之后,会把得到的类的实例缓存起来。 前言 只有光头才能变强 JVM在准备面试的时候就有看了,一直没时间写笔记。现在到了一家公司实习,闲的时候就写写,刷刷JVM博客,刷刷电子书。 学习JVM的目的也很简单...
阅读 981·2021-09-29 09:35
阅读 4562·2021-09-22 15:24
阅读 1427·2021-07-25 21:37
阅读 2140·2019-08-30 14:17
阅读 890·2019-08-30 13:56
阅读 2320·2019-08-29 17:07
阅读 1103·2019-08-29 12:44
阅读 2686·2019-08-26 18:26