记得刚开始学习Java的时候,老师说了很多在准备阶段生成,在准备阶段怎样怎样的,那时候很不理解为什么会有这么多的阶段,如果那时候对类的加载机制有了了解应该会理解的很快吧。
1.字节码解读
Java的字节码与其类的加载机制息息相关,众所周知,计算机只认识0和1,那么在程序运行的时候只有将人类语言变成计算机语言才能被计算机理解并且执行。
Java应该算是先解释后编译型语言,在Java诞生的时候,sun公司就喊着口号:“Write Once , Run Angwhere!” 。Java通过不同平台的JVM来实现了跨平台的功能。源码通过编译器来变成字节码文件,Java虚拟机负责载入和执行Java的字节码文件。
源码(.java)→编译器→字节码(.class)→JVM
可以通过最简单的helloworld来对其进行理解:
public class Demo{
public static void main(String args[]){
System.out.println("Hello World!");
}
}
对其使用javac进行编译后打开Demo.class文件可以看到:
这是一个人类基本看不懂的文件,这段字节码文件开头部分的CAFE BABE
被称为“魔数”,是JVM识别.class 文件的标志。其他的内容可以选择略过了。
2.类加载过程
Java的类加载过程可以分为五个阶段:载入、验证、准备、解析和初始化。这五个阶段是按照顺序进行的,但是在动态绑定下,解析阶段会发生在初始化阶段之后。
1)Loading(载入)
JVM 在该阶段的主要目的是将字节码从不同的数据源(可能是 class 文件、也可能是 jar 包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class
对象。
2)Verification(验证)
JVM 会在该阶段对二进制字节流进行校验,只有符合 JVM 字节码规范的才能被 JVM 正确执行。该阶段是保证 JVM 安全的重要屏障,下面是一些主要的检查。
- 确保二进制字节流格式符合预期(比如说是否以
cafe bene
开头)。 - 是否所有方法都遵守访问控制关键字的限定。
- 方法调用的参数个数和类型是否正确。
- 确保变量在使用之前被正确初始化了。
- 检查变量是否被赋予恰当类型的值。
3)Preparation(准备)
JVM 会在该阶段对类变量(也称为静态变量,static 关键字修饰的)分配内存并初始化(对应数据类型的默认初始值,如 0、0L、null、false 等)。
也就是说,假如有这样一段代码:
public String oh = "oh";
public static String yes = "yes";
public static final String ohyes = "ohyes";
oh 不会被分配内存,而 yes 会;但 yes 的初始值不是“王二”而是 null
。
需要注意的是,static final 修饰的变量被称作为常量,和类变量不同。常量一旦赋值就不会改变了,所以 ohyes 在准备阶段的值为ohyes而不是 null
。
4)Resolution(解析)
该阶段将常量池中的符号引用转化为直接引用。
符号引用以一组符号(任何形式的字面量,只要在使用时能够无歧义的定位到目标即可)来描述所引用的目标。
在编译时,Java 类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。
直接引用通过对符号引用进行解析,找到引用的实际内存地址。
5)Initialization(初始化)
该阶段是类加载过程的最后一步。在准备阶段,类变量已经被赋过默认初始值,而在初始化阶段,类变量将被赋值为代码期望赋的值。换句话说,初始化阶段是执行类构造器方法的过程。
用String来做一个例子。
String cmower = new String("ohyes");
上面这段代码使用了 new
关键字来实例化一个字符串对象,那么这时候,调用String类中的构1)启动类加载器(Bootstrap Class-Loader),加载 jre/lib 包下面的 jar 文件,比如说常见的 rt.jar。