JVM系列(二):JVM的类加载子系统

作者:陆金龙    发表时间:2022-11-20 05:39   

关键词类加载器,ClassLoader

1.类加载过程

  从大的方面包含三个阶段:加载、连接(验证、准备、解析)、初始化。

  

1.1 类的加载阶段

通过一个类的全限定名来获取定义此类的二进制字节流。可以从ZIP包中读取(JAR、WAR),从网络中读取(Applet),运行时计算生成(动态代理技术),有其他文件生成(由JSP文件生成Class类),从数据库中读取等等。

将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区中这个类的数据的访问入口。

1.2 连接阶段

验证:验证的目的是确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。大致上会完成:文件格式验证、元数据验证、字节码验证、符号引用验证。

准备:正式为类变量(static修饰的变量)分配内存并设置类变量初始值,即数据类型的零值

解析:虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这7类符号引用进行。

1.3 初始化阶段

初始化阶段是执行类构造器<clinit>()方法的过程。

而在初始化阶段,则会根据类中定义的Java程序代码去初始化类变量和其他资源

2.类加载器

JVM支持两种类型的类加载器,分别为启动类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。
 
Bootstrap ClassLoader:启动类加载器,也叫引导类加载器,使用C和C++语言实现,是虚拟机自身的一部分。
 
User-Defined ClassLoader:这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。Java语言提供的自动义类加载器有扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader)。除此之外,用户可以定义自己的类加载器,继承ClassLoader。
 
自定义类加载器都直接或间接地继承ClassLoader。
扩展类加载器和应用程序类加载器都继承URLClassLoader,间接地继承了抽象类ClassLoader,它们的继承关系如下:
ExtClassLoader和AppClassLoader都是定义在Launcher类的内部。
 

2.1 启动类加载器(Bootstrap ClassLoader)

负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别)类库加载到虚拟机内存中。
 
例如:加载C:\Program Files\Java\jdk1.8.0_191\jre\lib下的jar类库。

2.2 扩展类加载器(Extension ClassLoader)

由sun.misc.Launcher$ExtClassLoader实现,开发者可以直接使用扩展类加载器。

ExtClassLoder中有一个parent变量是BootstrapClassLoader,但是不是继承(extends)关系。

ExtClassLoader负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。
 
通过System.getProperty("java.ext.dirs").split(";"); 可以得到java.ext.dirs的路径,本机运行结果如下:
C:\Program Files\Java\jre1.8.0_191\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
 
例如:加载C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext下的jar类库。

2.3 应用程序类加载器(Application ClassLoader)

由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。
 
AppClassLoader中有一个parent变量是ExtClassLoader,但是不是继承(extends)关系,二者都继承自URLClassLoader。
 
负责加载用户类路径(CLASSPATH)所指定的类库,或者系统属性java.class.path(系统类加载器加载字节码class的路径)指定路径下的类库。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器
 
注1:Java虚拟机在加载某个类时,会按照CLASSPATH指定的目录顺序去查找需要的类(.class)。这个路径可以是多个,用;隔开。需要把jdk安装目录下的lib子目录中的dt.jar和tools.jar设置到CLASSPATH中,当前目录“.”也加入到该变量中。JDK 5.0以后,默认会在当前的工作目录及JDK的lib目录中查找所需要的类,即使不设置 CLASSPATH 环境变量,也可以正常编译和运行 Java 程序。
注2:通过System.getProperty("java.class.path").split(";") 可以得到java.class.path的结果,本机运行结果如下:
C:\Users\Administrator\.m2\repository\*\*.jar(若干个)
C:\Program Files\Java\jdk1.8.0_191\lib\jconsole.jar
C:\Program Files\Java\jdk1.8.0_191\lib\tools.jar
E:\2.project-code\klcms\klcms-web\target\classes
E:\2.project-code\klcms\klcms-provider\target\classes
 
 

3.类的加载流程

应用程序都是由Bootstrap ClassLoader、Extension ClassLoader、Application ClassLoader这三种类加载器相互配合进行加载(有必要也可以加入自己定义的类加载器)。

3.1 类加载器之间的关系——双亲委派模型

类加载器之间的关系如下图所示,被称为类加载器的双亲委派模型。Bootstrap ClassLoader、Extension ClassLoader、Application ClassLoader、User Defined ClassLoader四者是包含关系,不是继承关系。

双亲委派模型要求除了顶层的启动类加载器外,其余的类加器都要有自己的父类加载器(不是继承关系,是组合关系),父类加载器通过getParent()获取。由于Bootstrap ClassLoader是由C和C++语言实现的,ExtClassLoader使用getParent()得到null,并不能真正获取到Bootstrap ClassLoader。getParent()返回null,意味着父类加载器就是启动类加载器了。

3.2 类加载的流程——双亲委派模型的工作过程

一个类加载器收到了类加载的请求,不会自己直接加载这个类,而是把这个请求委派给父类加载器完成,每一个层次的类加载器都是这样,最终所有的类加载请求都传送到启动类加载器中。当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己加载。

双亲委派模型对于保证Java程序的稳定运行很重要。

(1)向上委派

AppClassLoader是加载一个新的class类时,不直接进行加载,而是向上委派给ExtClassLoader,查找ExtClassLoader是否缓存了这个class类,如果有则返回。如果没有则继续委派给BootstrapClassLoader,如果BootstrapClassLoader中有缓存则返回,没有缓存则BootstrapClassLoader查找%JAVA_HOME%/lib下的jar与class类文件,如果有则加载返回,如果没有则开始向下查找。

(2)向下查找

BootstrapClassLoader中不能加载这个类,则向下委派给ExtClassLoader执行加载,尝试查找ExtClassLoader对应路径%JAVA_HOME%/lib/ext下(或者被java.ext.dirs系统变量指定的路径下)的文件,如果有则加载返回,没有则继续向下到AppClassLoader查找加载,负责加载环境变量classpath或者系统属性java.class.path指定路径下的类库。如果AppClassLoader找不到则会抛出找不到class类异常。

双亲委派模型进行类加载的好处:避免类的重复加载;保护程序安全,避免核心API被随意篡改;避免用户自定义类使用核心API的包名。

4.使用类加载器

4.1 获取类加载器

方式一:获取当前类的classLoader
this.getClass().getClassLoader ()
 
方式二:获取系统的classLoader
ClassLoader.getSystemClassLoader ()
 
方式三:获取当前线程上下文的ClassLoader
Thread.currentThread () .getContextClassLoader ()
 
方式四:获取调用者的classLoader
DriverManager.getCallerclassLoader ()

4.2 类加载器的方法

getParent()      返回该类加载器的父类加载器

loadClass(String name)    加载名称为name的类,返回java.lang.Class类的实例

findClass(String name)     查找名称为name的类,返回java.lang.Class类的实例

findLoadedClass(String name)   查找名称为name的已被加载过的类,返回java.lang.Class类的实例

defineClas(String name,byte[],int off,int len) 把字节数组b中的内容转换为一个Java类,返回java.lang.Class类的实例

resolveClass(Class<?> c)

5.用户自定义类加载器

继承抽象类ClassLoader(或者继承URLClassLoader),JDK1.2之前,重写loadClass方法。JDK1.2之后,重写findClass方法。