Java类加载运行全过程-Java Debug 笔记

运行Java程序

还记得刚开始学习Java的时候,首先学的就是如何在文本编辑器里面写Java代码,然后编译和运行。

  1. 新建Hello.java文件,然后用记事本打开,写上以下代码并保存。
public class Hello {
    public static void main(String[] args) {
        System.out.println("hello java");
    }
}
复制代码
  1. 打开控制台,定位到Hello.java文件所在目录 cd /Users/stardust/Desktop/TestCode
  2. 使用命令 javac -d . Hello.java进行编译,这时候会多出一个文件Hello.class
  3. 使用命令 java Hello 就能在控制台中打印hello java

这大概就是最初的时光了。为何这样就可以运行代码,里面的机制是怎么样的?现在我们来一探究竟。

类加载过程

当我们使用java命令来运行某个类的main方法启动程序时,首先得启动一个JVM容器,然后把这个类的字节码文件加载到JVM中。

Java的底层其实是C++实现的,所以当我们输入java命令的时候,会调用底层的jvm.dll来创建Java核心虚拟机(JVM),接着还会创建一个根(引导)类加载器实例。引导类加载器会加载JDK中的sun.misc.Launcher类并调用其中的getLauncher()方法获取Launcher实例。通过Launcher实例可以获取到AppClassLoader实例。通过AppClassLoader去载入Hello.class。加载完成后,JVM会执行类中的main方法。程序结束后,JVM会自行销毁。如下图所示:

image.png

需要注意的是:

  • Launcher类实现了饿汉式的单例模式,所以每个JVM只有一个Launcher实例。
  • sun.misc.Launcher#getClassLoader返回的是AppClassLoader实例,Launcher类里面有sun.misc.Launcher.AppClassLoader和sun.misc.Launcher.ExtClassLoader两种加载器,逻辑上有父子关系,但是ExtClassLoader并不是AppClassLoader的父类。事实上这两个类加载器都是继承了java.net.URLClassLoader

loadClass加载类的步骤

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成这3个步骤,所以有时也把这3个步骤统称为类加载或类初始化。

类的加载

类加载指的是将类的 class 文件读入内存,并为之创建一个 java.lang.Class 对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。

类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM 提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。

通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源。

  • 从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式。
  • 从JAR包加载class文件,这种方式也是很常见的,前面介绍JDBC编程时用到的数据库驱动类就放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。
  • 通过网络加载class文件。
  • 把一个Java源文件动态编译,并执行加载。类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。

类的连接

当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接又可分为如下3个阶段。

  • (1)验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。
  • (2)准备:类准备阶段则负责为类的静态Field分配内存,并设置默认初始值。
  • (3)解析:将类的二进制数据中的符号引用替换成直接引用。

类的初始化

在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对静态Field进行初始化。在Java类中对静态Field指定初始值有两种方式:① 声明静态Field时指定初始值;② 使用静态初始化块为静态Field指定初始值。

JVM初始化一个类包含如下几个步骤。

  • (1)假如这个类还没有被加载和连接,则程序先加载并连接该类。
  • (2)假如该类的直接父类还没有被初始化,则先初始化其直接父类。
  • (3)假如类中有初始化语句,则系统依次执行这些初始化语句。

当执行第2个步骤时,系统对直接父类的初始化步骤也遵循此步骤1~3;如果该直接父类又有直接父类,则系统再次重复这 3个步骤来先初始化这个父类……依此类推,所以JVM最先初始化的总是java.lang.Object类。当程序主动使用任何一个类时,系统会保证该类以及所有父类(包括直接父类和间接父类)都会被初始化。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享