共计 2463 个字符,预计需要花费 7 分钟才能阅读完成。
导读 | 我们可以通过 javac 命令将 Java 程序的源代码编译成 Java 字节码,即我们常说的 class 文件,这是我们通常意义上理解的编译。 |
我们可以通过 javac 命令将 Java 程序的源代码编译成 Java 字节码,即我们常说的 class 文件,这是我们通常意义上理解的编译。但是,字节码并不是机器语言,要想让机器能够执行,还需要把字节码翻译成机器指令,这个过程是通过解释器实现的,叫解释执行。注意:大家别把编译和解释执行混淆了,而后面所说的后端编译过程是 JVM 为提高效率做的优化,在不同的虚拟机实现中,执行引擎在执行字节码的时候,通常会有解释执行 (通过解释器执行) 和编译执行 (通过即时编译器产生本地代码执行) 两种选择,也可能两者兼备。所以大家可以思考下,Java 到底是属于编译型语言还是解释器语言呢?那为什么 java 不直接编译成可执行文件呢,其实主要还是为了实现跨平台。Java 源码通过编译成字节码,然后通过不同平台的虚拟机解释执行,从而实现一次编译,到处运行的跨平台的效果。
Java 语言的编译期分为前端编译和后端编译两个阶段
前端编译是指把 *.java 文件转变成 *.class 文件的过程,包括词法分析、语法分析、语义分析与中间代码生成。
主要有下面几个步骤:
在部分商用虚拟机中,Java 程序最初是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为热点代码
为了提高热点代码的执行效率,在运行时, 虚拟机将会把这些代码编译成与本地平台相关的机器码
完成这个任务的后端编译器称为即时编译器(JIT 编译器)
既然 Java 编译是指将 Java 源码编译成 Java 字节码的过程
那么 Java 反编译简单说就是指根据 Java 字节码翻译成源码的过程
首先这个源码是字符编码,字节码是二进制字节流,并且源码是给人看的,字节码是给虚拟机看的
因此如果想给人看,需要将字节码转为源码。如果想给虚拟机执行,需要将源码编译成字节码,当我们有类文件想看源码时,可以采用反编译的方式实现
比如想了解某个 Java 语法糖编译后,再反编译是什么样的; 别人给你发一个 jar 包,你需要看其中某个类是怎么写的,等此类情况都可以考虑是用 Java 反编译
GitHub:https://github.com/java-decompiler/jd-gui
官网:http://java-decompiler.github.io/
下载后将类文件或者 jar 包直接拖动到界面即可
下载地址:https://github.com/deathmarine/Luyten/releases
官网:https://arthas.aliyun.com/doc/
可以使用 jad 命令将 JVM 中运行的 class 的 byte code 反编译成 java 代码
javap 是 jdk 自带的一个工具,可以对代码反编译,也可以查看 java 编译器生成的字节码
直接通过 javap -help 查看其用法
用法: javap
其中, 可能的选项包括:
-help --help -? 输出此用法消息
-version 版本信息
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的 / 公共类和成员
-package 显示程序包 / 受保护的 / 公共类
和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的
系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示最终常量
-classpath 指定查找用户类文件的位置
-cp 指定查找用户类文件的位置
-bootclasspath 覆盖引导类文件的位置
基本使用:
javac Test.java
javap -c Test.class
jclasslib 是一种可视化的字节码查看工具,可以直接在 IDEA 插件安装
安装以后,在 IDEA 编译源码后,可以选择 View”->“Show Bytecode With Jclasslib 即可查看字节码
可以直观地看到 class 文件包含基本信息、常量池、接口信息、字段信息、方法信息和属性信息
其中方法信息又包含行号表、局部变量表,异常表等
要读懂字节码指令涉及的知识很多,之后的文章会通过案例详细讲解 class 文件结构和字节码指令的执行过程
推荐两本非常经典的图书:《深入理解 Java 虚拟机》、《Java 虚拟机规范》
下面看一个简单和常见的案例:
public class ForEachDemo {public static void main(String[] args) {List data = new ArrayList();
data.add("a");
data.add("b");
for (String str : data) {System.out.println(str);
}
}
}
我们直接在 IDEA 对该类文件进行编译,然后再 target 目录中寻找该类,双击打开,得到下面的反编译源码:
public class ForEachDemo {public ForEachDemo() { }
public static void main(String[] args) {List data = new ArrayList();
data.add("a");
data.add("b");
Iterator var2 = data.iterator();
while(var2.hasNext()) {String str = (String)var2.next();
System.out.println(str);
}
}
}
从上述反编译代码可以清楚地看到,原始代码中没有编写构造方法时,编译器会自动生成一个默认构造方法;foreach 循环来遍历 list 时,底层通过 iterator 来实现