java javac 浅析

lunchbox 发布于 2024-11-02 52 次阅读


java javac 浅析与使用

笔者能力有限,暂做不到看源码来分析具体流程,只能说个大概,如有不足之处,请评论纠正。

计算机是个巨大的黑盒,硬件-操作系统-应用程序每一层都有着层层障壁。java在这黑盒套黑盒中又套了层jvm,变成操作系统-jvm-java应用,真是令人困恼。

网上看java教程,都只说明javac编译一下java文件成字节码文件就可以通过java这个可执行文件直接运行。可看着才14kb的java,javac文件总是不明所以,为什么能运行呢?特别是javac还是由java编写的,为什么它能是个可执行文件,背后究竟做了什么?

openjdk17中的相关文件

//javac的源文件
jdk/src/jdk.compiler/share/classes/com/sun/tools/javac/Main.java
//通用启动器文件
jdk/src/java.base/share/native/launcher/main.c
//java源文件
jdk/src/java.base/share/native/libjli/java.c
//javac编译后的class被存放于modules中,可通过以下指令查看
# 解压 lib/modules 文件
jimage extract --dir extracted_modules {openjdk17-target}/lib/modules

# 查找 javac 的 class 文件
find extracted_modules -name "*javac*
#指定java可执行文件生成的 配置文件代码段
#make/CompileJavaModules.gmk
define DeclareCompileJavaRecipe
  $1-java:
    +($(CD) $(TOPDIR)/make && $(MAKE) $(MAKE_ARGS) \
    $(patsubst %,-I%/modules/$1,$(PHASE_MAKEDIRS)) \
        -f CompileJavaModules.gmk MODULE=$1)
endef
$(foreach m, $(JAVA_MODULES), $(eval $(call DeclareCompileJavaRecipe,$m)))
ALL_TARGETS += $(JAVA_TARGETS)

以上是我在openjdk17源码与发行包中找到的相关文件,不难看出,java就是个jvm启动器,通过命令行参数提供jvm的启动参数与具体的执行目标。javac就是启动jvm并提供执行具体的javac字节码入口目标的程序。

抛开工具直接使用java javac

工具jar工程

//不写包是可以编译并打包成jar的
//但是在另一个拥有包名的工程中将无法引用
package com.ppp;

public class HelloWorld{
  public static void printMessage(){
    System.out.println("Hello,World");
  }
}
#将当前位置的java文件编译到指定的包名目录下
#会在out下生成 com/ppp/HelloWorld.class
javac -d out HelloWorld.java
#没有main入口 java命令不能启动
#但你可以通过jshell调用
jshell --class-path ./out

jshell> import com.ppp.HelloWorld;
jshell> HelloWorld.printMessage();
#没有main方法 也没有引入外部lib
#所以不加MANIFEST.MF
jar cvf jar/helloworld.jar -C out .
#这个jar无法运行 因为没有指定main入口 jvm不知道怎么启动

Main工程

//package和路径完全可以不相同
//想想maven的工程路径src/main/java/...
package com.formerlunchbox;

//本地工程的类文件
import com.formerlunchbox.tool.PrintTool;
//jar的类文件
import com.ppp.HelloWorld;

public class Example {
  public static void main(String[] args) throws Exception {
    for(String s:args){
        PrintTool.printMessage(s);
    }
    //假设HelloWorld无包名 则会报错
    //无法 import HelloWorld;
    HelloWorld.printMessage();
  }
}

package com.formerlunchbox.tool;

public class PrintTool {
  public static void printMessage(String str) {
    System.out.println(str);
  }
}

MANIFEST.MF 指定main入口和引用的jar路径
多个jar 空格隔开

Manifest-Version: 1.0
Main-Class: com.formerlunchbox.Example
Class-Path: lib/helloworld.jar
#cp选项指定JAR包路径 多个lib 用:隔开
#-cp lib/helloworld.jar:lib/anotherlib.jar
javac -cp lib/helloworld.jar -d out src/main/java/com/formerlunchbox/*.java  src/main/java/com/formerlunchbox/tool/*.java

#在指定out目录下 引用jar 提供main路径
java -cp out:lib/helloworld.jar com.formerlunchbox.Example 1

# 按指定的方式生成jar文件
jar cmvf META-INF/MANIFEST.MF example.jar -C out .
# 运行jar文件
java -jar example.jar 1

手打几遍就知道maven的好了!

题外话-关于jvm

众所周知,绝大多数jvm都是使用c++编写的,但总是有那么几个jvm不同凡响,用到了java来实现。

  • JikesRVM 和 Maxine VM
    通过一个小的 C/C++ 引导程序启动自己的 Java 运行时。在启动之后,它们的 JIT 编译器(也是用 Java 写的,由c/c++引导程序来负载)会将 Java 代码即时编译为机器码,让代码直接在硬件上执行。因此,虽然用 Java 写的,但它们通过自举和自编译的方式,不依赖其他 JVM。

  • GraalVM
    Graal 编译器作为 HotSpot JVM 的插件工作。它在 HotSpot 中作为 JIT 编译器来优化和运行 Java 字节码,并将代码编译为机器码直接运行。因此 GraalVM 依赖 HotSpot 提供的 JVM 环境。

java还是离不开大肠包小肠!