当你运行Hello World的时候, JVM都发生了什么?

288 阅读3分钟

昨天面试的时候被面试官问了一道这个问题, 答的磕磕绊绊, 感觉自己对于JVM的运行过程的了解不够清晰, 现在记录一下, 试着用自己的语言来清晰的说明一下整个运行过程. 哎, 感觉自己好菜...


在理解JVM的运行过程之前, 我们首先要知道JVM的体系结构是什么样子的, 如下图:

image.png

  1. 运行时数据区 当java文件通过编译之后, 生成的class字节码文件会由Class Loader类加载器进行加载, 然后交给执行引擎去执行, 在执行的过程中会产生一些数据, 这些数据会被存储到一块内存区域, 这就是运行时数据区.

  2. 程序计数器 用于记录当前线程正在执行的字节码指令的位置. 由于虚拟机是多线程的, 我们需要记录下来不同线程的执行位置以便于切换线程和分配CPU, 所以程序计数器是线程私有的.

  3. 虚拟机栈 每个java方法执行的时候会创建一个栈帧, 用于存储局部变量等信息. 当方法执行完毕之后, 从栈弹出.

  4. 本地方法栈 和虚拟机栈类似, 本地方法的执行会用到本地方法栈.

5.方法区 用于存储已被虚拟机加载的类信息, 常量以及静态变量等数据.

6.堆 主要用来存放程序运行过程中创建的实例对象. 大部分的垃圾回收是发生在这里的.


现在了解了JVM的这些内存结构之后, 我们来看执行Hello World的代码的时候到底发生了什么.

假设我们的代码如下所示:

// HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

然后我们通过IDE或者使用命令javac HelloWorld.java进行编译, 得到HelloWorld.class文件.

接下来当我们通过IDE或者java HelloWorld去执行这个Class文件的时候, 字节码文件(Class文件)通过类加载器(Class Loader)加载完毕并交付给执行引擎去执行. 这个时候类加载机制会把HelloWorld中的常量, 静态变量, 类的信息加载到方法区. 如果我们需要创建这个类的实例的话, 需要使用new关键字和后面的参数去方法区找到类的相关信息.

当类加载完毕, JVM会检查程序的入口, 并根据public static关键字去找到跟主类关联的main方法, 在这个例子中, 虚拟机会去找到HelloWolrd.class中的main函数, 并生成一个栈帧入栈到虚拟机栈中. 如果执行的过程中有用到本地方法, 则会在本地方法栈中出入栈.

补充:

  • main函数中的参数String[] args在JVM中的运行过程: 系统首先加载String[].class字节码文件到方法区, 然后为变量args(存放的是String[]的地址)在主线程的虚拟机栈中创建一个新的栈帧并指向堆中存放的数据(数据是String[]).