Java学习之编译、反编译以及字节码入门
注:本文所述的编译与反编译知识点绝大部分基于Java语言。
编译
什么是编译
利用编译程序从源语言编写的源程序产生目标程序的过程。用编译程序产生目标程序的动作。编译就是把高级语言变成计算机可以识别的2进制语言,计算机只认识1和0,编译程序把人们熟悉的语言换成2进制的。编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。
编译方式
编译方式,即编译的分类;一种分类是静态编译和动态编译。
静态编译(static compilation),也叫事前编译(ahead-of-time compilation,AOT);通俗一点地说,就是在编译时把所有的模块都编译进目标程序(比如exe)里去,当启动这个目标程序时所有模块都加载进来。
动态编译(dynamic compilation),在运行时进行编译;通俗一点地说,就是在编译时,所需的模块没有都编译进去,一般情况下你可以把那些模块都编译成dll,这样启动程序(初始化)时这些模块不会被加载,而是在运行时,用到哪个模块就调用哪个模块。
区分对比
动态编译的可执行文件需要附带一个的动态链接库。在执行时,需要调用其对应动态链接库中的命令。所以其优点一方面是缩小了执行文件本身的体积,另一方面是加快了编译速度,节省了系统资源。缺点一是哪怕是很简单的程序,只用到了链接库中的一两条命令,也需要附带一个相对庞大的链接库;二是如果其他计算机上没有安装对应的运行库,则用动态编译的可执行文件就不能运行。
静态编译就是编译器在编译可执行文件的时候,将可执行文件需要调用的对应动态链接库(.so或.lib)中的部分提取出来,链接到可执行文件中去,使可执行文件在运行的时候不依赖于动态链接库。所以其优缺点与动态编译的可执行文件正好互补。
Java编译时间:是指虚拟机的JIT编译器编译热点代码的时间,Java源代码编译出的class文件中存储的是字节码,虚拟机通过解释的方式执行字节码文件,比起C、C++的本地二进制代码速度慢很多,JVM内置两个运行时编译器,如果一个Java方法,被调用次数达到一定程度,就会被判定为热代码,交给JIT编译器编译为本地代码,提高运行速度。C、C++是静态编译,而Java大多是动态编译。
Java有三种编译方式: 前端编译、JIT编译(即时编译,just-in-time compile)、AOT编译(静态提前编译)
Java程序代码需要编译后才能在虚拟机中运行,编译涉及到:编译原理、语言规范、虚拟机规范、本地机器码优化等;
2、JIT编译狭义来说是当某段代码即将第一次被执行时进行编译,因而叫“即时编译”。JIT编译是动态编译的一种特例。JIT编译一词后来被泛化,时常与动态编译等价;但要注意广义与狭义的JIT编译所指的区别。
3、自适应动态编译(adaptive dynamic compilation)也是一种动态编译,但它通常执行的时机比JIT编译迟,先让程序“以某种式”先运行起来,收集一些信息之后再做动态编译。这样的编译可以更加优化。
前端编译
把Java源码文件(.java)编译成Class文件(.class)的过程;也即把满足Java语言规范的程序转化为满足JVM规范所要求格式的文件;
优点:
- 这阶段的优化是指程序编码方面的;
- 许多Java语法新特性(泛型、内部类等语法糖),是靠前端编译器实现的,而不是依赖虚拟机;
- 编译后的Class文件可以直接给JVM解释器解释执行,省去编译时间,加快启动速度;
缺点:
- 对代码运行效率几乎没有任何优化措施;
- 解释执行效率较低;
- 前端编译器:Oracle javac、Eclipse JDT中的增量式编译器(ECJ)等;
JIT编译
也叫后端编译,JVM内置,在运行时把Class字节码文件编译成本地机器码的过程;
JIT优化技术:
- 内联:在前面的描述中可以知道,可以避免方法跳跃。
- 垃圾代码(称之死代码更恰当):当某些对象存在于字节码中且不被使用时,编译器可以决定从机器代码中删除它们。
- 循环优化:编译器可以组织并优化循环执行顺序或对尾递归优化成for循环等,以此来优化CPU所执行的代码。
- 用实现方法替换接口方法:当给定接口的一个方法有且仅由一个对象实现时,编译器可以决定直接使用实现的方法,以避免在运行时绑定真正实现的方法所引起的开销。
优点:
- 通过在运行时收集监控信息,把"热点代码"(Hot Spot Code)编译成与本地平台相关的机器码,并进行各种层次的优化;
- 可以大大提高执行效率;
缺点:
- 收集监控信息影响程序运行;
- 编译过程占用程序运行时间(如使得启动速度变慢);
- 编译机器码占用内存;
- JIT编译器:HotSpot虚拟机的C1、C2编译器等;
JIT编译速度及编译结果的优劣,是衡量一个JVM性能的很重要指标;所以对程序运行性能优化集中到这个阶段;也就是说可以对这个阶段进行JVM调优;
AOT编译
程序运行前,直接把Java源码文件(.java)编译成本地机器码的过程;
优点:
- 编译不占用运行时间,可以做一些较耗时的优化,并可加快程序启动;
- 把编译的本地机器码保存磁盘,不占用内存,并可多次使用;
缺点:
- 因为Java语言的动态性(如反射)带来额外的复杂性,影响静态编译代码的质量;
- 一般静态编译不如JIT编译的质量,这种方式用得比较少;
- AOT编译器:JAOTC、GCJ、Excelsior JET、ART (Android Runtime)等;
组合
目前Java体系中主要还是采用前端编译+JIT编译的方式,如JDK中的HotSpot虚拟机,其运作过程大体如下:
- 首先通过前端编译把符合Java语言规范的程序代码转化为满足JVM规范所要求Class格式;
- 然后程序启动时Class格式文件发挥作用,解释执行,省去编译时间,加快启动速度;
- 针对Class解释执行效率低的问题,在运行中收集性能监控信息,得知"热点代码";
- JIT逐渐发挥作用,把越来越多的热点代码"编译优化成本地代码,提高执行效率;
编译工具
- javac
IDEA 查看 class 文件的字节码
javac 编译.java 文件到.class 文件:javac Hello.java
然后 javap 查看 class 文件:javap -c Hello.class
File->Settings->Tools->External Tool
不要自以为是地使用
J
A
V
A
H
O
M
E
JAVA_HOME
JAVAHOME
反编译
什么是反编译
反编译,有一个很专业很高大上的术语,即计算机软件反向工程(Reverse engineering)也称为计算机软件还原工程,是指通过对他人软件的目标程序(可执行程序)进行逆向分析、研究工作,以推导出他人的软件产品所使用的思路、原理、结构、算法、处理过程、运行方法等设计要素,某些特定情况下可能推导出源代码。反编译作为自己开发软件时的参考,或者直接用于自己的软件产品中。
class文件打破C或者C++等语言所遵循的传统,使用这些传统语言写的程序通常首先被编译,然后被连接成单独的、专门支持特定硬件平台和操作系统的二进制文件。通常情况下,一个平台上的二进制可执行文件不能在其他平台上工作。而Java class文件是可以运行在任何支持Java虚拟机的硬件平台和操作系统上的二进制文件。
JAVA最突出的跨平台优势使得它不能被编译成本地代码,而要以中间代码的形式运行在虚拟机环境中,这使得JAVA的反编译要比别的高级语言容易实现,并且反编译的代码经过优化后几乎可以与源代码相媲美。
反编译,就是通过*.class文件得到其 .java文件。
什么时候需要反编译
1、只有一个类的class文件(或者是jar、war包),但是看不懂class文件或者说看起来效率非常低下,此时可以考虑反编译。
2、有时想知道Java一些语法糖实现细节,可以借助反编译。
反编译工具
又名反编译器,解码器,将目标程序码反转成源代码。两者之间的转换不是一一对应的:两段完全不同的Java程序也可能生成完全相同的字节码,有时需要一些试探才能更加接近源码。
- javap
JDK自带。 - Jad
在线反编译工具:http://www.javadecompilers.com/jad
JAD本身是一个命令行工具,没有图形界面,上述的这些工具大多是在JAD内核的基础之上加一个图形界面(比如jd-GUI)。JAD是使用MS Visual C++开发的,运行速度非常快,可以处理很复杂的JAVA编译文件。JAD提供很多参数用于灵活应付多种加密手段,使得反编译得到代码更加优化和易读:
-d 用于指定输出文件的目录
-s 输出文件扩展名(默认为: .jad),通常都会把输出文件扩展名直接指定为.java,以方便修改的重新编译。
-8 将Unicode字符转换为ANSI字符串,如果输出字符串是中文的话一定要加上这个参数才能正确显示。
命令:Jad –d c:\\javasource –s .java -8 javatest.class
将当前目录下的javatest.class反编译为javatest.java并保存在c:\javasource目录里,其中的提示输出为中文,而不是Unicode代码。
- JODE
官网:http://jode.sourceforge.net/
JODE,即Java Optimize and Decompile Environment,纯JAVA开发,众多Java反编译软件的核心引擎, 能应付一些常见的加密手段,例如混淆技术等。提供一个可运行jar。很古老,很久没有更新,源码拖管于SourceForge。 - Java Decompiler
- Fernflower decompiler
IDEA使用的反编译器。
字节码
字节码文件,也就是.class 文件。Java一次编译到处运行,得益于JVM对字节码文件的解析。不论该字节码文件来自何方,由哪种编译器编译,甚至是手写字节码文件,只要符合java虚拟机的规范,那么它就能够执行该字节码文件。
public class Demo {
private static final Integer NUM = 1;
public static void main(String[] args) {
}
}
习惯使用IDE的我们,此时如果想要看字节码文件,不能使用IDE查看class文件,IDE会自动将class文件反编译成和源文件非常类似的文件:
public class Demo {
private static final Integer NUM = 1;
public Demo() {
}
public static void main(String[] args) {
}
}
上面即为使用IDEA自带的Fernflower decompiler反编译之后得到的文件,增加一个默认的构造方法。
想要查看字节码文件需要使用文本编辑器,比如sublime text。
看到的是这样的效果:
cafe babe 0000 0034 001f 0a00 0500 160a
0017 0018 0900 0400 1907 001a 0700 1b01
0003 4e55 4d01 0013 4c6a 6176 612f 6c61
6e67 2f49 6e74 6567 6572 3b01 0006 3c69
6e69 743e 0100 0328 2956 0100 0443 6f64
6501 000f 4c69 6e65 4e75 6d62 6572 5461
626c 6501 0012 4c6f 6361 6c56 6172 6961
626c 6554 6162 6c65 0100 0474 6869 7301
002e 4c63 6f6d 2f63 7472 6970 2f74 6f75
7274 6169 6c6f 722f 636f 6d6d 6f6e 7365
7276 6963 652f 636f 7265 2f44 656d 6f3b
0100 046d 6169 6e01 0016 285b 4c6a 6176
612f 6c61 6e67 2f53 7472 696e 673b 2956
0100 0461 7267 7301 0013 5b4c 6a61 7661
2f6c 616e 672f 5374 7269 6e67 3b01 0008
3c63 6c69 6e69 743e 0100 0a53 6f75 7263
6546 696c 6501 0009 4465 6d6f 2e6a 6176
610c 0008 0009 0700 1c0c 001d 001e 0c00
0600 0701 002c 636f 6d2f 6374 7269 702f
746f 7572 7461 696c 6f72 2f63 6f6d 6d6f
6e73 6572 7669 6365 2f63 6f72 652f 4465
6d6f 0100 106a 6176 612f 6c61 6e67 2f4f
626a 6563 7401 0011 6a61 7661 2f6c 616e
672f 496e 7465 6765 7201 0007 7661 6c75
654f 6601 0016 2849 294c 6a61 7661 2f6c
616e 672f 496e 7465 6765 723b 0021 0004
0005 0000 0001 001a 0006 0007 0000 0003
0001 0008 0009 0001 000a 0000 002f 0001
0001 0000 0005 2ab7 0001 b100 0000 0200
0b00 0000 0600 0100 0000 0700 0c00 0000
0c00 0100 0000 0500 0d00 0e00 0000 0900
0f00 1000 0100 0a00 0000 2b00 0000 0100
0000 01b1 0000 0002 000b 0000 0006 0001
0000 000c 000c 0000 000c 0001 0000 0001
0011 0012 0000 0008 0013 0009 0001 000a
0000 0020 0001 0000 0000 0008 04b8 0002
b300 03b1 0000 0001 000b 0000 0006 0001
0000 0008 0001 0014 0000 0002 0015
一堆16进制的字节。
Java字节码的总览图,一共含有10部分。
反汇编
比反编译更底层的过程。。
参考
Java三种编译方式: 前端编译 JIT编译 AOT编译
一文让你明白 Java 字节码