转载

JVM执行程序时,都发生了什么?

JVM执行程序时,都发生了什么?

当一个Java程序被执行时,JVM都做了什么?今天这篇文章,记录了我的探索过程。让我们开始吧!

下文使用的程序选自 Log4j 文档中的示例程序。

JDK 版本 :

java version "12" 2019-03-19

Java(TM) SE Runtime Environment (build 12+33)

Java HotSpot(TM) 64-Bit Server VM (build 12+33, mixed mode, sharing)

package self.samson.bu;

// Import log4j classes.
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

public class MyApp {

    // Define a static logger variable so that it references the
    // Logger instance named "MyApp".
    private static final Logger logger = LogManager.getLogger(MyApp.class);

    public static void main(final String... args) {

        // Set up a simple configuration that logs on the console.

        logger.trace("Entering application.");
        Bar bar = new Bar();
        if (!bar.doIt()) {
            logger.error("Didn't do it.");
        }
        logger.trace("Exiting application.");
    }
}

package self.samson.bu;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

public class Bar {
    static final Logger logger = LogManager.getLogger(Bar.class.getName());

    public boolean doIt() {
        logger.entry();
        logger.error("Did it again!");
        return logger.exit(false);
    }
}

JVM执行过程

首先,先看一下 Java Language Specification, jls 中描述的JVM执行过程。

  1. 加载主类
  2. 链接:验证、预处理和解析(可选地)
  3. 初始化:执行初始化器
  4. 执行主方法

接下来,结合jls中定义步骤,分析一下JVM执行 MyAppmain 方法的过程。

加载主类 self.samson.bu.MyApp

当JVM首次尝试执行 MyApp 类的 main 方法时,会发现该类尚未被加载到内存中,即内存中不存在与 MyApp.class 相对应的 Class 对象。此时,JVM会使用类加载器,将 MyApp.class 加载到内存中。调试发现,此时使用的类加载器为 AppClassLoader 。如果类加载器无法在 classpath 中找到 MyApp.class ,JVM将抛出 NoClassDefFoundError

链接

主类被加载过后,JVM会对加载的类进行链接。链接过程主要包括验证、预处理和解析。

  1. 验证。首先,JVM会验证加载的 MyApp 类是否是格式良好的、是否具有适当的符号表、以及是否遵循Java编程语言和JVM语义规范。
  2. 预处理。其次,JVM会为类中的静态域(类变量和常量)分配空间,并把这些空间初始化为默认值。注意,此时不会执行任何源代码,初始化器的执行会在初始化阶段,而不是预处理阶段。除了为 MyApp 中的静态变量( logger )分配空间外,JVM还需要为一些它内部使用的对象(例如方法表等)分配空间。
  3. 解析。最后,检查 MyApp 对其他类或接口的引用,加载这些类或接口,并检查这些引用是否正确。这一步是可选地,如果没有引用其他类,此阶段可以跳过。这里需要加载 org.apache.logging.log4j.Logger

初始化

初始化阶段会执行类变量初始化器(initializer)和静态初始化器。在 MyApp 中,只有一个类变量初始化器,即 logger = LogManager.getLogger(MyApp.class); 。当JVM尝试调用 org.apache.logging.log4j.LogManager 类的 getLogger 方法时,它会发现该类尚未加载。JVM会按照之前的进程去加载、链接、初始化 LogManager 类。 LoggerManager 包含多个类变量初始化器和一个静态初始化器。如下所示:

// 类变量初始化器
public static final String FACTORY_PROPERTY_NAME = "log4j2.loggerContextFactory";
public static final String ROOT_LOGGER_NAME = "";
private static final Logger LOGGER = StatusLogger.getLogger();
private static final String FQCN = LogManager.class.getName();
private static volatile LoggerContextFactory factory;
// 静态初始化器
static {
    PropertiesUtil managerProps = PropertiesUtil.getProperties();
    String factoryClassName = managerProps.getStringProperty("log4j2.loggerContextFactory");
    if (factoryClassName != null) {
        try {
            factory = (LoggerContextFactory)LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class);
        } catch (ClassNotFoundException var8) {
            LOGGER.error("Unable to locate configured LoggerContextFactory {}", factoryClassName);
        } catch (Exception var9) {
            LOGGER.error("Unable to create configured LoggerContextFactory {}", factoryClassName, var9);
        }
    }
    // more...        
}

这些初始化器的执行,会按照文本顺序进行。执行过程还会引起JVM对其他类(例如 org.apache.logging.log4j.status.StatusLoggerorg.apache.logging.log4j.util.PropertiesUtil 等)的加载。过程与上面介绍的 MyApp 类的加载过程类似,此处就不再过多地叙述。

等所有的依赖都加载并初始化完毕后, MyApp 类的初始化才会结束。此外, MyApp 的初始化必须在它的直接父类或父接口的初始化之后完成。前面的代码中, MyApp 的直接父类只有 java.lang.Object ,如果它还没被初始化过,会先初始化 Object 类。

类的初始化过程包括:执行静态初始化器和执行静态变量(类变量)的初始化器。接口的初始化过程包括:执行域(常量)的初始化器。jls中定义了类或接口 T 初始化发生的时机:

  • T 为类时,创建 T 的对象时,会触发 T 的初始化;
  • T 中定义的静态方法被调用时,会触发 T 的初始化;
  • T 中定义的静态域被赋值时,会触发 T 的初始化;
  • T 中定义的非常量域被使用时,会触发 T 的初始化。

Initialization of an interface does not, of itself, cause initialization of any of its superinterfaces.

执行主方法

MyApp 的初始化过程结束后,JVM会再次尝试执行它的 main 方法。jls中定义了两种可接受的 main 方法声明方式:

public static void main(String[] args)
public static void main(String... args)
原文  https://segmentfault.com/a/1190000021455990
正文到此结束
Loading...