1. Class文件结构
本文在整理时参考了以下几位大佬的笔记:
1.1 概述
1.1.1 字节码文件的跨平台性
1、Java语言:跨平台的语言(Write once, run anywhere)- 当Java源代码成功编译成字节码后,如果想在不同的平台上面运行,则无须再次编译
- 这个优势不再那么吸引人了。Python、PHP、Per1、Ruby、Lisp等有强大的解释器。
- 跨平台似乎已经快成为一门悟言必选的特性。
Java虚拟机不和包括Java 在内的任何语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联。无论使用何种语言进行软件开发,只要能将源文件编译为正确的Class文件,那么这种语言就可以在Java虚拟机上执行。可以说,统一而强大的Class文件结构,就是]ava虚拟机的基石、桥梁。

Java Language and Virtual Machine Specifications

所有的JVM全部遵守Java虚拟机规范,也就是说所有的JVM环境都是一样的,这样一来字节码文件可以在各种JVM上运行。
3、想要让一个Java程序正确地运行在JVM中,Java源码就必须要被编译为符合JVM规范的字节码。
- 前端编译器的主要任务就是负责将符合Java语法规范的Java代码转换为符合JVM规范的字节码文件
- javac是一种能够将Java源码编译为字节码的前端编译器。
- Javac编译器在将Java源码编译为一个有效的字节码文件过程中经历了4个主要的步骤,分别是词法解析、语法解析、语义解析以及生成字节码。

Oracle的JDK软件包括两部分内容:
- 一部分是将Java源代码编译成Java虚拟机的指令集的编译器
- 另一部分是用于实现Java虚拟机的运行时环境
1.1.2 Java的前端编译器

前端编译器vs后端编译器
Java源代码的编译结果是字节码,那么肯定需要有一种编译器能够将Java源码编译为字节码,承担这个重要责任的就是配置在path环境变量中的javac编译器。javac是一种能够将Java源码编译为字节码前端编译器。
HotSpot VM并没有强制要求前端编译器只能使用javac来编译字节码,其实只要编译结果符合JVM规范都可以被JVM所识别即可。在Java的前端编译器领域,除了javac之外,还有一种被大家经常用到的前端编译器,那就是内置在Eclipse中的ECJ (EclipseCompiler for Java)编译器。和javac的全量式编译不同,ECJ是一种增量式编译器。
- 在Eclipse中,当开发人员编写完代码后,使用“Ctr1+S”快捷键时,ECJ编译器所采取的编译方案是把未编译部分的源码逐行进行编译,而非每次都全量编译。因此ECT的编译效率会比javac更加迅速和高效,当然编译质量和javac相比大致还是一样的。
- ECJ不仅是Eclipse的默认内置前端编译器,在Tomcat中同样也是使用ECJ编译器来编译jsp文件.由于ECJ编译器是采用GPLv2的开源协议进行源代码公开,所以,大家可以登录eclipse官网下载ECJ编译器的源码进行二次开发。
- 默认情况下,Intellij IDEA使用javac编译器。(还可以自己设置为AspectJ编译器ajc)
前端编译器并不会直接涉及编译优化等方面的技术,而是将这些具体优化细节移交给HotSpot的JIT编译器负责。
复习:AOT(静态提前编译器,Ahead Of Time Compiler)
1.1.3 透过字节码指令看代码细节
BAT面试题
- ① 类文件结构有几个部分?
- ② 知道字节码吗?字节码都有哪些?
Integer x =5; int y = 5;比较x == y都经过哪些步骤?
代码举例
代码举例1:
- 代码
- 字节码
public class IntegerTest {
public static void main(String[] args) {
Integer x = 5;
int y = 5;
//自动拆箱 x拆箱为int 5
System.out.println(x == y);//true
Integer i1 = 10;
Integer i2 = 10;
//Integer缓存 -128到127
System.out.println(i1 == i2);//true
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4);//false
}
}
0 iconst_5 //把int常数5入操作数栈
1 invokestatic #2 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;> //调用Integer.valueOf方法
4 astore_1 //出栈保存到局部变量表槽1
5 iconst_5 //把int常数5入操作数栈
6 istore_2 //出栈保存到局部变量表槽2
7 getstatic #3 <java/lang/System.out : Ljava/io/PrintStream;> //调用打印类
10 aload_1 //读取局部表里表槽1入操作数栈
11 invokevirtual #4 <java/lang/Integer.intValue : ()I> //调用方法Integer.intValue 拆箱为基本数据类型int类型
14 iload_2 //读取局部表里表槽2入操作数栈
15 if_icmpne 22 (+7) //比较
18 iconst_1
19 goto 23 (+4) //跳转到23行命令
22 iconst_0
23 invokevirtual #5 <java/io/PrintStream.println : (Z)V> //打印
26 bipush 10 //10入操作数栈
28 invokestatic #2 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;> //调用Integer.valueOf方法
31 astore_3 //出栈保存到局部变量表槽3
32 bipush 10 //10入操作数栈
34 invokestatic #2 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;> //调用Integer.valueOf方法
37 astore 4 //出栈保存到局部变量表槽4
39 getstatic #3 <java/lang/System.out : Ljava/io/PrintStream;>
42 aload_3
43 aload 4
45 if_acmpne 52 (+7)
48 iconst_1
49 goto 53 (+4)
52 iconst_0
53 invokevirtual #5 <java/io/PrintStream.println : (Z)V>
56 sipush 128
59 invokestatic #2 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
62 astore 5
64 sipush 128
67 invokestatic #2 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
70 astore 6
72 getstatic #3 <java/lang/System.out : Ljava/io/PrintStream;>
75 aload 5
77 aload 6
79 if_acmpne 86 (+7)
82 iconst_1
83 goto 87 (+4)
86 iconst_0
87 invokevirtual #5 <java/io/PrintStream.println : (Z)V>
90 return
Integer i1 = 10;底层字节码是调用Integer.valueOf(int i)方法,如果[-128,127]范围内则返回缓存里的,否则new一个Integer类型的对象返回。

low的值为-128;high的值为127。

代码举例2:
- 代码
- 字节码
public class StringTest {
public static void main(String[] args) {
//底层new一个StringBuilder进行拼接
String str = new String("hello") + new String("world");
String str1 = "helloworld";
System.out.println(str == str1); //false
String str2 = new String("helloworld");
System.out.println(str == str2); //false
}
}
0 new #2 <java/lang/StringBuilder> //new一个StringBuilder
3 dup //复制一份入操作数栈
4 invokespecial #3 <java/lang/StringBuilder.<init> : ()V> //StringBuilder构造器调用
7 new #4 <java/lang/String> //new 一个String
10 dup //复制一份入操作数栈
11 ldc #5 <hello> //把 hello 字符串加载到字符串常量池
13 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V> //String构造器
16 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;> // 调用StringBuilder.append方法:hello
19 new #4 <java/lang/String> //new 一个String
22 dup
23 ldc #8 <world> //把 world 字符串加载到字符串常量池
25 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V>
28 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;> // 调用StringBuilder.append方法:world
31 invokevirtual #9 <java/lang/StringBuilder.toString : ()Ljava/lang/String;> //拼接完成调用StringBuilder.toString方法
34 astore_1 //保存到局部变量表槽1
35 ldc #10 <helloworld> //把 helloworld 字符串加载到字符串常量池
37 astore_2 //保存到局部变量表槽2
//比 较输出
38 getstatic #11 <java/lang/System.out : Ljava/io/PrintStream;>
41 aload_1
42 aload_2
43 if_acmpne 50 (+7)
46 iconst_1
47 goto 51 (+4)
50 iconst_0
51 invokevirtual #12 <java/io/PrintStream.println : (Z)V>
//下面是差不多的,不分析了
54 new #4 <java/lang/String>
57 dup
58 ldc #10 <helloworld>
60 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V>
63 astore_3
64 getstatic #11 <java/lang/System.out : Ljava/io/PrintStream;>
67 aload_1
68 aload_3
69 if_acmpne 76 (+7)
72 iconst_1
73 goto 77 (+4)
76 iconst_0
77 invokevirtual #12 <java/io/PrintStream.println : (Z)V>
80 return
字符串拼接由StringBuilder实现。
执行完 String str = new String("hello") + new String("world");
- 堆中:有
StringBuilder对象,new String("hello")匿名对象和new String("world")匿名对象和str对象。 - 常量池有:
hello,world两个对象
代码举例3
对象实例化步骤
- 代码
- Father <init> 字节码
- Son <init> 字节码
- main方法字节码
class Father {
int x = 10;
public Father() {
this.print();
x = 20;
}
public void print() {
System.out.println("Father.x = " + x);
}
}
class Son extends Father {
int x = 30;
public Son() {
this.print();
x = 40;
}
@Override
public void print() {
System.out.println("Son.x = " + x);
}
}
public class SonTest {
public static void main(String[] args) {
Father f = new Son();
System.out.println(f.x);
}
}
0 aload_0 //加载this对象
1 invokespecial #1 <java/lang/Object.<init> : ()V> //调用父类Object构造器初始化方法
4 aload_0 //加载this对象
5 bipush 10 //10入操作数栈
7 putfield #2 <com/atguigu/java/Father.x : I> //设置成员变量的值
10 aload_0 //加载this对象
11 invokevirtual #3 <com/atguigu/java/Father.print : ()V> //调用方法Father.print()
14 aload_0 //加载this对象
15 bipush 20 //20入操作数栈
17 putfield #2 <com/atguigu/java/Father.x : I> //设置成员变量的值
20 return //返回
0 aload_0
1 invokespecial #1 <com/atguigu/java/Father.<init> : ()V> //调用Father构造器初始化方法
4 aload_0
5 bipush 30
7 putfield #2 <com/atguigu/java/Son.x : I> //设置30在上面Father.<init>之后执行的
10 aload_0
11 invokevirtual #3 <com/atguigu/java/Son.print : ()V>
14 aload_0
15 bipush 40
17 putfield #2 <com/atguigu/java/Son.x : I>
20 return
0 new #2 <com/atguigu/java/Son> //new Son
3 dup //复制一份到栈
4 invokespecial #3 <com/atguigu/java/Son.<init> : ()V> ///调用Son构造器初始化方法
7 astore_1
8 getstatic #4 <java/lang/System.out : Ljava/io/PrintStream;>
11 aload_1
12 getfield #5 <com/atguigu/java/Father.x : I> //成员变量无多态:Father.x
15 invokevirtual #6 <java/io/PrintStream.println : (I)V>
18 return
输出
Son.x = 0
Son.x = 30
20