点击 Run 按钮时,JVM 做了什么?
考虑如下 Java 代码——
1 |
|
当执行 Main 类的 main 方法时(这里就假设我们在使用 IDEA,点击了 main 前面的:arrow_forward: 按钮吧!)
- 首先,调用 javac,将 Main.java 编译成 java 字节码文件,即 Main.class。
- 使用 java 执行 Main.class 文件。这时,一个 JVM 进程被启动,它通过 classpath 路径找到 Main.class 文件,使用类加载器(
ClassLoader
)将类信息加载到运行时数据区的方法区内。然后,在堆中生成指向类信息的Class
对象。这是 Java 的类加载过程(这里得提出一个问题,Class 对象中究竟存储了什么?不会只有一个指向类信息的指针和一堆偏移量吧?)。然后是链接和初始化。 - JVM 找到 Main 的主程序入口(应该是通过 Class 对象找到的),执行 main 方法。
- main 方法的第一句为
String stud = new Student("arisa")
,它要求 JVM 创建一个 Student 对象的实例。JVM 将首先检查 Student 类是否被加载(如果没有加载,则方法区中不存在 Student 类的信息,因此无法创建实例)。这里显然是未被加载的。因此,将会进行类的加载过程。
进行处理的首先是AppClassLoader
。(当然,在这之前总得将 Student.java 编译并弄到内存里)它将委托它的父类加载器ExtClassLoader
进行加载,ExtClassLoader
再委托它的父类加载器BootstrapClassLoader
进行加载。BootstrapClassLoader
没有父类加载器,因而它将尝试加载,加载失败后,它将返回,由ExtClassLoader
进行加载,其也将加载失败,由AppClassLoader
进行加载。这就是类加载器的所谓双亲委派机制。Main.class 也是如此加载的。 - 类加载后,JVM 在堆中分配一个新的 Student 实例的内存。然后调用构造函数初始化 Student 的实例。每个 Student 的实例都持有对方法区中 Student 类信息的引用。之后,将该实例的地址赋给 stud。这里的 stud 称为对象引用,它指向的堆中实际的对象称为对象实例。stud 存储在栈中,而它指向的对象实例存在于堆中。
- 然后它准备执行 stud.sayName()。JVM 根据 stud 这个引用找到在堆中的 Student 对象实例,然后根据 Student 对象持有的引用定位到方法区中 Student 的类信息中的方法表,获得 sayName() 的字节码地址。
- 将 stud 作为参数(应该……如此),执行 sayName() 方法。(就像 python 中定义实例方法需要显式指明 self 一样,实例方法其实是需要对象实例作为参数的,因为实例方法其实也是只存在一个的,就如静态方法一样,它无法保存类的实例的地址在方法里,所以类的实例要传递给实例方法)。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 协议 ,转载请注明出处!