参考:
Idea 使用技巧记录_source code recreated from a .class file by intell_hresh的博客-CSDN博客
深入理解Java Class文件格式(一)_昨夜星辰_zhangjg的博客-CSDN博客
实践详解javap命令(反编译字节码)_天然玩家的博客-CSDN博客
一、编译
1.1 编程语言
在介绍编译和反编译之前,我们先来简单介绍下编程语言(Programming Language)。
编程语言(Programming Language)分为低级语言(Low-level Language)和高级语言(High-level Language)。
机器语言(Machine Language)和汇编语言(Assembly Language)属于低级语言,直接用计算机指令编写程序。
而C、C++、Java、Python等属于高级语言,用语句(Statement)编写程序,语句是计算机指令的抽象表示。
1.2 编译
上面提到语言有两种,一种低级语言,一种高级语言。
简单的理解:低级语言是计算机认识的语言、高级语言是程序员认识的语言。
那么如何从高级语言转换成低级语言呢?这个过程其实就是编译。
将便于人编写、阅读、维护的高级计算机语言所写作的源代码程序,翻译为计算机能解读、运行的低阶机器语言的程序的过程就是编译。负责这一过程的处理的工具叫做编译器
现在我们知道了什么是编译,也知道了什么是编译器。不同的语言都有自己的编译器,Java 语言中负责编译的编译器是一个命令:javac
当我们写完一个 HelloWorld.java 文件后,我们可以使用 javac HelloWorld.java
命令来生成 HelloWorld.class 文件,这个 class 类型的文件是JVM可以识别的文件。通常我们认为这个过程叫做 Java 语言的编译。其实,class 文件仍然不是机器能够识别的语言,因为机器只能识别机器语言,还需要 JVM 再将这种 class文件类型字节码转换成机器可以识别的机器语言。
javac是收录于JDK中的Java语言编译器。该工具可以将后缀名为.java的源文件编译为后缀名为.class的可以运行于Java虚拟机的字节码。
我们都知道Java有自己的语言规范,JVM也有自己的解析规则,Java程序能够运行在虚拟机上,得益于javac编译器,将Java语言编译成字节码,javac属于静态编译,因为它不能直接把程序源码编译成机器可以识别的机器码,javac将程序整个完全编译成字节码后,才能交由虚拟机解释执行,这个过程需要耗费的时间(对比于其他可一边编译一边执行的语言)和空间(整个完全编译完成)都比较大。
1.2.1 编译举例
编写hello world程序,HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
for (int i =0;i<=5;i++){
System.out.println("hello world");
}
}
}
实现编译的方式,有两种。
方式一
javac HelloWorld.java
在当前路径下,生成HelloWorld.class 文件。
方式二:
在idea中,直接运行程序,会自动在target文件下,生成HelloWorld.class文件。
1.2.2 查看字节码class文件
1、在idea中,查看
在idea中,直接查看HelloWorld.class,idea自动对该文件进行了反编译。
观察发现,源文件,与编译后,再反编译的文件有点小小的区别。
我们对于优化二字定义稍微宽松一些,因为Javac编译器对代码运行效率几乎没有任何优化措施,虚拟机设计团队将对性能的优化集中到了后端的JIT中,这样可以让那些不是由javac产生的class文件(Groovy JRuby等语言的class文件)也同样能享受到编译器优化带来的好处。但是Javac做了许多针对编码的优化措施来改善程序员的编码风格和提高编码效率。相当多新生的Java语法特性,都是靠编译器的语法糖来实现,而不是依赖虚拟机的底层改进来支持。Java中JIT在运行期的优化过程对于程序运行更加重要,而前端编译器在编译期的优化过程对于程序编码来说关系更加密切。
javac在编译Java程序时会进行一些优化。这些优化旨在提高程序的性能和执行效率。
在编译过程中,javac会对代码进行静态分析和优化。它会检查代码中的错误并进行纠正,同时还会尝试改善生成的字节码的质量。例如,javac可以对常量进行折叠(constant folding),消除无用的代码(dead code elimination),将循环展开(loop unrolling),减少函数调用开销等等。这些优化可以减少代码的执行时间和内存消耗。
当你提到源文件与编译再反编译之后的文件之间有小小的区别时,这可能是由于编译器对代码进行了优化所致。优化可能导致生成的字节码与原始源代码略有不同,但仍保持相同的逻辑功能。这是正常的行为,因为优化的目标是提高代码的执行效率,而不是保持与原始源代码的完全一致性。
需要注意的是,不同的编译器可能会应用不同的优化策略和技术,因此在不同的编译环境下,反编译后的代码可能会有细微差异。
1.2 使用txt文件查看
我们可以使用txt程序,来打开HelloWorld.class文件,查看字节码。
cafe babe 0000 0034 001e 0a00 0600 1009
0011 0012 0800 130a 0014 0015 0700 1607
0017 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 046d 6169
6e01 0016 285b 4c6a 6176 612f 6c61 6e67
2f53 7472 696e 673b 2956 0100 0d53 7461
636b 4d61 7054 6162 6c65 0100 0a53 6f75
7263 6546 696c 6501 000f 4865 6c6c 6f57
6f72 6c64 2e6a 6176 610c 0007 0008 0700
180c 0019 001a 0100 0b68 656c 6c6f 2077
6f72 6c64 0700 1b0c 001c 001d 0100 1179
6f75 6875 612f 4865 6c6c 6f57 6f72 6c64
0100 106a 6176 612f 6c61 6e67 2f4f 626a
6563 7401 0010 6a61 7661 2f6c 616e 672f
5379 7374 656d 0100 036f 7574 0100 154c
6a61 7661 2f69 6f2f 5072 696e 7453 7472
6561 6d3b 0100 136a 6176 612f 696f 2f50
7269 6e74 5374 7265 616d 0100 0770 7269
6e74 6c6e 0100 1528 4c6a 6176 612f 6c61
6e67 2f53 7472 696e 673b 2956 0021 0005
0006 0000 0000 0002 0001 0007 0008 0001
0009 0000 001d 0001 0001 0000 0005 2ab7
0001 b100 0000 0100 0a00 0000 0600 0100
0000 0300 0900 0b00 0c00 0100 0900 0000
4900 0200 0200 0000 1603 3c1b 08a3 0011
b200 0212 03b6 0004 8401 01a7 fff0 b100
0000 0200 0a00 0000 1200 0400 0000 0500
0700 0600 0f00 0500 1500 0800 0d00 0000
0900 02fc 0002 01fa 0012 0001 000e 0000
0002 000f
class文件是一种8位字节的二进制流文件(文件通过二进制存储,以8个字节为一组,所以以16进制展示), 各个数据项按顺序紧密的从前向后排列, 相邻的项之间没有间隙, 这样可以使得class文件非常紧凑, 体积轻巧, 可以被JVM快速的加载至内存, 并且占据较少的内存空间。 我们的Java源文件, 在被编译之后, 每个类(或者接口)都单独占据一个class文件, 并且类中的所有信息都会在class文件中有相应的描述, 由于class文件很灵活, 它甚至比Java源文件有着更强的描述能力。
class文件中的信息是一项一项排列的, 每项数据都有它的固定长度, 有的占一个字节, 有的占两个字节, 还有的占四个字节或8个字节, 数据项的不同长度分别用u1, u2, u4, u8表示, 分别表示一种数据项在class文件中占据一个字节, 两个字节, 4个字节和8个字节。 可以把u1, u2, u3, u4看做class文件数据项的“类型” 。
1.3 反编译
java提供了可以解析class文件称为字节码文件的工具,把这个class文件,翻译成字节码文件,告诉虚拟机你要去做什么操作。
javap -v
举例:
javap -v HelloWorld.class
源码:
public class HelloWorld {
public static void main(String[] args) {
String s1 = "hello" + "world";
String s2 = "hello".concat(("world"));
System.out.println(s1 == s2);
}
}
编译:
javac HelloWorld.java
反编译
javap -v HelloWorld.class
类文件大小:size 639 bytes
类文件MD5:MD5 checksum 402978bba57b05316db982dabcf3e7f3
类文件源文件名称: Compiled from "HelloWorld.java"
常量池:Constant pool: