竹笋

首页 » 问答 » 灌水 » JVM类加载机制
TUhjnbcbe - 2023/7/19 20:29:00

1、什么是类加载

Java语言号称一次编译到处运行,而实现这一目标的重要环节就是类加载。

Java的编译过程

如上图,类的加载是指把编译后的.class类文件的二进制数据读取到内存中,并根据它创建一个java.lang.Class对象,用来封装类在元数据空间的数据结构。

理解这句话需要对JVM的内存模型及运行机制有一定了解。不过在这里可以通俗的理解为将.class文件加载进入JVM并将其转化为描述对象的Class对象存放在堆中(Heap)。

类在JVM中的生命周期如下,

类的生命周期

这一过程包括:加载,连接,初始化,使用,卸载。这里我们重点讨论的是加载机制。

类加载的过程是这样的:

类加载过程在第三节和第四节将详细介绍这个过程。

这其中包含了三个自带的加载器和一个用户自定义加载器,

引导类加载器Bootstrap-ClassLoader基于C/C++实现,负责加载Java的核心类库JAVA_HOME\jre\lib\rt.jar,该加载器不继承自ClassLoader抽象类,并且只加载包名为java、javax、sun等开头类,起到对核心源码的保护作用。

扩展类加载器Extension-ClassLoader,基于Java语言,由sun.misc.Launcher$ExtClassLoader实现,派生于ClassLoader抽象类,从java.ext.dirs系统变量指定的路径中的加载类库,或者JDK安装目录jre\lib\ext目录下加载。

系统类加载器Application-ClassLoader,基于Java语言,由sun.misc.Launcher$ExtClassLoader实现,它负责加载环境变量ClassPath指定的类库,如果在应用程序中没有自定义类加载器,一般情况下作为程序中默认的类加载器。

2、双亲委派模型

双亲委派的指的是,类加载由最底下的自定义加载器开始,将加载请求委派给上级,如果上级已经加载过该类,则直接加载完成,如果没有则继续递归至顶级Bootstrap加载器,然后父类判断如果该类不属于自己的加载范畴则委派子类加载。继续往下递归。

双亲委派的目的是,

防止加载同一个.class。通过委托去询问上级是否已经加载过该.class,如果加载过了,则不需要重新加载。

保证核心.class不被篡改。通过委托的方式,保证核心.class不被篡改,即使被篡改也不会被加载,即使被加载也不会是同一个class对象,因为不同的加载器加载同一个.class也不是同一个Class对象。这样则保证了Class的执行安全

所以双亲委派的核心是安全和效率。

3、违背双亲委派

3.1、JDBC打破双亲委派

在JDBC4.0之后实际上我们不需要再调用Class.forName来加载驱动程序了,我们只需要把驱动的jar包放到工程的类加载路径里,那么驱动就会被自动加载。这个自动加载采用的技术叫作SPI,数据库驱动厂商也都做了更新。可以看一下jar包里面的META-INF/services目录,里面有一个java.sql.Driver的文件,文件里面包含了驱动的全路径名。

如此一来,我们只需要如下语句就可以获得数据库连接。

Connectioncon=DriverManager.getConnection(url,username,password);

但是,类加载器受到加载范围的限制,在某些情况下父类加载器无法加载到需要的文件,需要委托子类加载器去加载class文件。

JDBC的Driver接口就定义在JDK中,其实现由各个数据库的服务商来提供,比如MySQL驱动包。riverManager类中要加载各个实现了Driver接口的类,然后进行管理,但是DriverManager位于JAVA_HOME中jre/lib/rt.jar里所有的class,

所以我们只能由子类加载器去加载Driver实现,这就破坏了双亲委派模型。为此DriverManager封装了静态代码块来实现这一加载过程。

static{loadInitialDrivers();println("JDBCDriverManagerinitialized");}privatestaticvoidloadInitialDrivers(){AccessController.doPrivileged(newPrivilegedActionVoid(){publicVoidrun(){ServiceLoaderDriverloadedDrivers=ServiceLoader.load(Driver.class);IteratorDriverdriversIterator=loadedDrivers.iterator();try{while(driversIterator.hasNext()){driversIterator.next();}}catch(Throwablet){//Donothing}returnnull;}});}

静态代码块中调用loadInitialDrivers()方法,并调用ServiceLoader.load(Driver.class)加载所有在META-INF/services/java.sql.Driver文件里边的类到JVM内存,完成驱动的自动加载。

3.2、Tomcat打破双亲委派

出于以下原因,Tomcat需要违背双亲委派机制:

一个web容器可能需要部署多个app,不同app可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,确保相互隔离。

部署在同一个web容器中相同类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机,必然会冗余。

web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。

web容器要支持jsp的修改,我们知道,jsp文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情,否则要你何用?所以,web容器需要支持jsp修改后不用重启。

显然,双亲委派已经不适用于web容器。

如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加载器只会加载一份。这是双亲委派模型的隔离性。

这需要独立的JSP类加载器,在JSP修改后重新加载它们。

Tomcat是如何实现类加载的呢

Tomcat类加载结构

CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader则是Tomcat自己定义的类加载器,它们分别加载/

1
查看完整版本: JVM类加载机制