java语言是编译执行的_Java也可以不用编译直接执行了?

我们都知道java是静态语言,也就是说,如果你想执行java程序,就必须先编译,再执行。

那本文为什么说,java可以不编译直接执行了呢?

其实,这个是OpenJDK11里新加的一个feature,目的是使单个文件的java源码可以无需编译,直接执行。

下面的JEP里对该特性做了详细的描述:

我们先写个小例子实验下:

$ 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源码可动态执行的特性还是给我们留下了很多想像空间,虽然其实现机制很粗暴,但对用户来说还算是友好的。

希望本篇文章能给各位同学带来一些收获。

完。

更多原创文章,请关注我微信公众号:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值