java 类加载过程及类加载器

2018-05-14 16:29:55   最后更新: 2018-05-16 16:28:51   访问数量:197




在上一篇日志中,我们通过一个实际的例子分析了 java 程序的初始化过程和类加载顺序

java 程序初始化过程

 

本篇日志中,我们来详细的了解一下 java 类加载器

 

java 的每个类都需要经历以下的七个阶段:

  1. 加载
  2. 验证
  3. 准备
  4. 解析
  5. 初始化
  6. 使用
  7. 卸载

 

 

 

我们编写了 .java 扩展名的类文件,存储了需要执行的代码逻辑,这些 .java 文件在编译期会被编译成 .class 文件,文件中保存了 java 代码经过转换后的虚拟机指令

当我们的程序需要使用到某个类的时候,java 虚拟机就会加载对应的 .class 文件并创建对应的 class 对象,加载到虚拟机内存,这就是加载过程

另外,值得注意的是,除了从 class 文件中读取字节码外,也可以从 jar 包或 war 包中读取,也可以在运行时动态计算生成(动态代理),也可以通过其他文件生成(如 jsp 文件转换)

 

这一阶段的目的是为了确保虚拟机的安全性与字节流的正确性

 

在准备阶段,虚拟机为类 static 变量分配内存,并且设置初始值 0,即使在类代码中为类 static 成员显式指定了初始值,仍然会在准备阶段赋值为 0,具体的显示指定的初始值将会在初始化阶段赋值

当然,这里不包含对 final static 成员的初始化,final static 成员的初始化是在编译期进行的,而实例变量是不会进行初始化的

类 static 成员分配在方法区中,实例变量则会随着对象一起在 java 堆中分配内存

 

解析阶段包含字段解析、类方法解析、接口方法解析

具体过程就是将 class 文件中常量池的符号引用替换为直接引用的过程

符号引用就是在 java 虚拟机规范中制定的一套字面量形式明确定义的常量标识,而直接引用则是指向目标的指针、相对偏移量或是一个能够间接定位到目标的句柄

 

这部分工作是类加载的最后阶段,也是我们上一篇日志中通过实际例子了解到的过程

如果该类具有父类,则对其父类先进行初始化,依次递归进行执行静态初始化器和静态成员变量默认初始化,执行构造方法

 

上面介绍了类的五个加载步骤,java 源代码通过 javac 编译器编译成 .class 文件,然后 jvm 来执行类文件中的字节码来执行程序

而类加载器的任务就是根据一个类的全限定名来读取此类的二进制字节流到 jvm 中,然后转换为一个与目标类对应的 java.lang.Class 对象实例以供 jvm 做后续处理

JVM 虚拟机提供了 3 种类加载器,引导(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器)

 

启动类加载器(Bootstrap)

启动类加载器主要加载的是 JVM 自身需要的类,他通过 C++ 实现,是虚拟机自身的一部分,是所有类加载器的父加载器,因此也被成为初始类加载器

它负责将 $JAVA_HOME/lib 路径下的核心类库或 -Xbootclasspath 参数指定的路径下的 rt.jar 包加载到内存中

出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类,启动类加载器无法被应用程序直接使用

 

扩展类加载器(Extension)

Bootstrap 加载器的子加载器,通过 java 编写,由 sun.misc.Launcher$ExtClassLoader 实现,主要加载 JAVA_HOME/lib/ext 目录中的类库,开发者可以自己使用扩展类加载器

java 程序中可以指定系统属性 -Djava.ext.dirs 来指定 Extension 加载器加载的目录

另外,在 java 程序中,通过 System.getProperty("java.ext.dirs") 可以返回系统变量 java.ext.dirs 所指定的路径

Extension 将加载类的请求先委托给它的父加载器,也就是 Bootstrap,如果没有成功加载的话,再从 java.ext.dirs 系统属性定义的目录下加载类

 

系统类加载器(System)

系统类加载器负责加载系统类路径 java -classpath 或 -D java.class.path 或 jar 包中的 Manifest 的 classpath 属性指定路径下的类库,也就是我们经常使用的 classpath 路径,也称为应用程序加载器

通过 ClassLoader.getSystemClassLoader() 方法可以获取到该类加载器

开发者可以直接使用系统类加载器,也可以通过派生创建自己的类加载器

 

总结

三个类加载器加载的类文件路径如下:

  • Bootstrap ClassLoader - JRE/lib/rt.jar
  • Extension ClassLoader - JRE/lib/ext 或者任何指向java.ext.dirs的路径
  • Application ClassLoader - CLASSPATH环境变量、-classpath or -cp 命令行选项,JAR 内部清单文件的类路径属性

 

通过 Class 对象的 getClassLoader 方法可以获取到加载他们的 ClassLoader 对象实例,我们通过下面的代码就可以看到具体对象所使用的 ClassLoader 对象:

public class TestController { public static void main(String [] str) { ClassLoader testClassLoader = TestController.class.getClassLoader(); if (testClassLoader == null) { System.out.println("classLoader is null"); } else { System.out.println("TestConctroller 的 ClassLoader 是:" + testClassLoader.toString()); } ClassLoader stringClassLoader = String.class.getClassLoader(); if (stringClassLoader == null) { System.out.println("classLoader is null"); } else { System.out.println("String 的 ClassLoader 是:" + stringClassLoader.toString()); } ClassLoader intClassLoader = int.class.getClassLoader(); if (intClassLoader == null) { System.out.println("classLoader is null"); } else { System.out.println("int 的 ClassLoader 是:" + intClassLoader.toString()); } } }

 

 

  • 输出了:

TestConctroller 的 ClassLoader 是:sun.misc.Launcher$AppClassLoader@18b4aac2

classLoader is null

classLoader is null

 

可以知道,我们自定义的类是 AppClassLoader 加载的,也就是我们上面介绍的 ApplicationClassLoader

但是,为什么 int 和 String 的类加载器都是 null 呢?这是因为他们都是 BootstrapClassLoader 加载的,BootstrapClassLoader 并不是 java 实现的,所以无法在 java 代码中获取到对应的加载器实例

 

深入理解Java类加载器(ClassLoader) -- https://blog.csdn.net/javazejian/article/details/73413292

类加载器的工作原理 -- http://www.importnew.com/6581.html

类加载器详解 -- https://www.cnblogs.com/dongguacai/p/5879931.html

 






技术帖      龙潭书斋      技术分享      class      system      java      jdk      加载      bootstrap      extension     


京ICP备15018585号