Java类加载机制简介
当调用java命令运行某个java进程时,该命令将会启动一个Java虚拟机进程,不管该Java程序有多么复杂,该程序启动了多少个线程,它们都处于该Java虚拟机进程里。同一个JVM的所有线程、所有变量都处于同一个进程里,它们都使用该JVM进程的内存区。当系统出现以下几种情况时,JVM进程将被终止:
- 程序运行到最后正常结束
- 程序运行到使用
System.exit()
或Runtime.getRuntime().exit()
代码处结束程序 - 程序执行过程中遇到未捕获的异常或错误而结束
- 程序所在平台强制结束了JVM进程
当程序主动使用某个类时,如果该类还未加载到内存中,则系统会通过加载、连接以及初始化三个步骤来对该类进行初始化。如果没有意外出现,JVM会将连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或类初始化。
(1)类的加载
类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。类的加载是由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。通过使用不同的类加载器,可以从不同来源加载类二进制数据,通常有如下几种来源:
- 从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式
- 从JAR包加载class文件,这种方式也是很常见的
- 通过网络加载class文件
- 把一个Java源文件动态编译,并执行加载
值得注意的是,类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。
(2)类的连接
当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接有可分为如下三个阶段:
- 验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
- 准备:类准备阶段负责为类的类变量分配内存,并设置默认初始值
- 解析:将类的二进制数据中的符号引用替换成直接引用
(3)类的初始化
在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对类变量进行初始化。在Java类中对类变量指定初始值有两种方式:一是声明类变量时指定初始值;二是使用静态初始化块为类变量指定初始值。
类加载器ClassLoader
类加载器负责将.class文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之生成对应的java.lang.Class对象。尽管在Java开发中无须过分关心类加载机制,但所有的编程人员都应该了解其工作机制。类加载器负责加载所有的类,并为所有的被载入内存中的类生成一个java.lang.Class实例。
正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。在java中,一个类用其全限定类名(包括包名和类名)作为标识。但在JVM中,一个被加载到JVM的类用其全限定类名和其类加载器作为其唯一标识。例如,如果在pg的包中有一个名为Person的类,被类加载器ClassLoader的实例k1负责加载,则该Person类对应的Class对象在JVM中被表示为(Person、pg、k1)
。这意味着两个类加载器加载的同名类(Person、pg、k1)
和(Person、pg、k2)
在JVM中被认为是不同的类。
Java中的类加载器大致可以分成两类,一类是系统提供的,另一类则是由Java应用开发人员编写的。所以JAVA平台中的类加载器有如下几种:
(1)引导类加载器(bootstrap class loader)
它用来加载Java的核心库,一般来说就是%JAVA_HOME%/jre/lib/rt.jar
中的各种类,如String、System等等。在Sun的JVM中,当执行java.exe命令时,使用-Xbootclasspath选项或使用-D选项指定sun.boot.class.path系统属性值可以指定加载附加的类。根类加载器非常特殊,它并不是java.lang.ClassLoader的子类,而是由JVM自身实现的。
(2)扩展类加载器(extensions class loader)
扩展类加载器负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext
或由java.ext.dirs
系统属性指定的目录)中JAR包的类。通过这种方式,就可以为Java扩展核心类以外的新功能,只要把自己开发的类打包成JAR文件,然后放入%JAVA_HOME%/jre/lib/ext
路径即可。
(3)系统类加载器(system class loader)
系统类加载器负载在JVM启动时加载来自java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。用户程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都是以系统类加载器作为父加载器。
(4)用户自定义的加载类
如下为各种类加载器的树状组织架构图:
备注:类的加载器也是一个类,真正去加载其他类的是加载器ClassLoader的对象。
类加载机制的方式
JVM的类加载机制主要有三种:
(1)全盘负责
所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入。
(2)父类委托
所有父类委托,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
(3)缓存机制
缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区中。所以在修改了Class后,需要重新启动JVM,程序所做的修改才会生效。
学习资料参考于:
https://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html