JVM加载class文件的原理机制

什么是类加载器?

类加载器(ClassLoader)就是在系统运行过程中动态的将字节码文件加载到 JVM 中的工具,基于这个工具的整套类加载流程,我们称作类加载机制。我们在 IDE 中编写的都是源代码文件,以后缀名为 .java 的文件形式存在于磁盘上,通过编译后生成后缀名为 .class 的字节码文件,ClassLoader 加载的就是这些字节码文件。

有哪些类加载器?

Java 默认提供了三个 ClassLoader,分别是根加载器(BootStrapClassLoader)、扩展类加载器(ExtClassLoader)、应用类加载器(AppClassLoader),依次前者分别是后者的「父加载器」。父加载器不是「父类」,三者之间没有继承关系,只是因为类加载的流程使三者之间形成了父子关系。
还有一种是用户自定义类加载器(java.lang.ClassLoader的子类)。从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM),PDM更好的保证了Java平台的安全性。在该机制中,JVM自带的BootStrapClassLoader是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对BootStrapClassLoader的引用。下面是关于几个类加载器的说明:
  BootStrapClassLoader:根加载器,它是脱离 Java 语言,使用 C/C++ 编写的类加载器,所以当你尝试使用 ExtClassLoader 的实例调用 getParent() 方法获取其父加载器时会得到一个 null 值。
boot1
  根加载器会默认加载系统变量 sun.boot.class.path 指定的类库(jar 文件和 .class 文件),默认是 $JRE_HOME/lib 下的类库,如 rt.jar、resources.jar 等,具体可以输出该环境变量的值来查看。
boot2
除了加载这些默认的类库外,也可以使用 JVM 参数 -Xbootclasspath/a 来追加额外需要让根加载器加载的类库。比如我们自定义一个 com.ganpengyu.boot.DateUtils 类来让根加载器加载。boot3
我们将其制作成一个名为 gpy-boot 的 jar 包放到 /Users/yu/Desktop/lib 下,然后写一个测试类去尝试加载 DateUtils。
boot4
运行这个测试类:boot5
可以看到输出为 true,也就是说加载 com.enjoy.boot.DateUtils 的类加载器在 Java 中无法获得其引用,而任何类都必须通过类加载器加载才能被使用,所以推断出这个类是被 BootStrapClassLoader 加载的,也证明了 -Xbootclasspath/a 参数确实可以追加需要被根加载器额外加载的类库。

总之,对于 BootStrapClassLoader 这个根加载器我们需要知道三点:

  1. 根加载器使用 C/C++ 编写,我们无法在 Java 中获得其实例
  2. 根加载器默认加载系统变量 sun.boot.class.path 指定的类库
  3. 可以使用 -Xbootclasspath/a 参数追加根加载器的默认加载类库

    ExtClassLoader:扩展类加载器,它是一个使用 Java 实现的类加载器(sun.misc.Launcher.ExtClassLoader),用于加载系统所需要的扩展类库。默认加载系统变量 java.ext.dirs 指定位置下的类库,通常是 $JRE_HOME/lib/ext 目录下的类库。
    ext1
    我们可以在启动时修改java.ext.dirs 变量的值来修改扩展类加载器的默认类库加载目录,但通常并不建议这样做。如果我们真的有需要扩展类加载器在启动时加载的类库,可以将其放置在默认的加载目录下。

总之,对于 ExtClassLoader 这个扩展类加载器我们需要知道两点:

  1. 扩展类加载器是使用 Java 实现的类加载器,我们可以在程序中获得它的实例并使用。
  2. 通常不建议修改java.ext.dirs 参数的值来修改默认加载目录,如有需要,可以将要加载的类库放到这个默认目录下。

AppClassLoader:应用类加载器,它和 ExtClassLoader 一样,也是使用 Java 实现的类加载器(sun.misc.Launcher.AppClassLoader)。它的作用是加载应用程序 classpath 下所有的类库。它是应用最广泛的类加载器,是我们最常打交道的类加载器,我们在程序中调用的很多 getClassLoader() 方法返回的都是它的实例。在我们自定义类加载器时如果没有特别指定,那么我们自定义的类加载器的默认父加载器也是这个应用类加载器。

总之,对于 AppClassLoader 这个应用类加载器我们需要知道三点:

  1. 应用类加载器是使用 Java 实现的类加载器,负责加载应用程序 classpath 下的类库。
  2. 应用类加载器是和我们最常打交道的类加载器。
  3. 没有特别指定的情况下,自定义类加载器的父加载器就是应用类加载器。

    自定义类加载器
    除了上述三种 Java 默认提供的类加载器外,我们还可以通过继承 java.lang.ClassLoader 来自定义一个类加载器。如果在创建自定义类加载器时没有指定父加载器,那么默认使用 AppClassLoader 作为父加载器。

类加载器的启动顺序

上文已经提到过 BootStrapClassLoader 是一个使用 C/C++ 编写的类加载器,它已经嵌入到了 JVM 的内核之中。当 JVM 启动时,BootStrapClassLoader 也会随之启动并加载核心类库。当核心类库加载完成后,BootStrapClassLoader 会创建 ExtClassLoader 和 AppClassLoader 的实例,两个 Java 实现的类加载器将会加载自己负责路径下的类库,这个过程我们可以在 sun.misc.Launcher 中窥见。

原理:

JVM中类的加载是由类加载器(ClassLoader)和它的子类来实现的。Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。
由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。
一、类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后Class对象还不完整,所以此时的类还不可用。
二、当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。
三、最后JVM对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。

转载于:https://blog.51cto.com/13890766/2352033

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值