专栏首页Linux内核及编程语言底层相关技术研究Java也可以不用编译直接执行了?

Java也可以不用编译直接执行了?

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

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

完。

本文分享自微信公众号 - Linux内核及JVM底层相关技术研究(ytcode)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-08-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 那些年用过的开源项目(一)

    近期微软收购github新闻热搜各大媒体,微软,曾经开源的极力反对者,近几年也是积极拥抱开源。我司近来也是积极的拥抱开源,贡献了包括ui5,cloudfoudr...

    Bruce Li
  • 函数式接口,方法和构造函数引用

    如何让现有的函数更友好地支持 Lambda,最好的方法是:增加函数式接口。所谓 “函数式接口”,是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是之前...

    happyJared
  • ECMAScript中类与继承详解(Java对比学习)

    如果声明一个一个类的时候没有声明构造函数,那么会默认添加一个空的构造函数,构造函数在new实例化一个对象的时候会被调用

    coder_koala
  • 从Rust到远方:WebAssembly 星系

    来源:https://mnt.io/2018/08/22/from-rust-to-beyond-the-webassembly-galaxy/

    MikeLoveRust
  • 性能测试调优经验总结

    之前做过一些性能测试及调优相关的工作,也参加过相关的一些培训,想写一篇文章记录用过的一些工具和一些经验总结。

    Bruce Li
  • 关于编码的那些事

    之前做一个POC的时候,Vicky同学遇到一个关于编码的问题,问到我,我觉得当时没有解释得很清楚,于是决定查阅相关的资料文档,写一篇文章,记录这个问题及对背后的...

    Bruce Li
  • 知识体系、算法题、教程、面经,这是一份超赞的AI资源列表

    照例先放上 GitHub 地址:https://github.com/Awesome-Interview/Awesome-Interview#LeetCode,...

    CDA数据分析师
  • 干货收藏 | Java程序员必备的一些流程图

    来源:juejin.im/post/5d214639e51d4550bf1ae8df

    Java团长
  • Arrays.asList存在的坑

    来源:juejin.im/post/5d10e52ee51d454f6f16ec11

    Java团长
  • 微信小程序自动化方案之准备

    用户5521279

扫码关注云+社区

领取腾讯云代金券