JVM 类加载机制

用户头像
Alex🐒
关注
发布于: 2020 年 07 月 22 日

类的生命周期



JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化。

  1. 加载

将 .class 文件内容读入内存(通过全限定名找到 .class 文件)。

类加载过程中的第一个阶段,会在内存中生成一个代表这个类的 java.lang.Class 对象。这里不一定非得要从一个 .class 文件获取,可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理)等。



  1. 链接



验证

验证字节码文件的正确性

这一阶段的主要目的是为了确保 .class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。



准备

给类的静态变量分配内存,并赋予默认值。

static int a = 10; // 准备阶段会赋值为 0
// 将 a 赋值为 10 的指令存放于类构造方法中
static final int a = 10; // 准备阶段会赋值为 10
// 编译阶段生成 ConstantValue 属性,准备阶段会根据 ConstantValue 直接赋值



解析

类装载器装入类所引用的其他所有类,将常量池中的符号引用替换为直接引用的过程。

符号引用就是 class 文件中的:CONSTANT_Class_info、CONSTANT_Field_info、CONSTANT_Method_info 等类型的常量。



符号引用

符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。



直接引用

直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。



  1. 初始化

为类的静态变量赋予正确的初始值,执行静态代码块。

前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码

类加载器



启动类加载器(Bootstrap Class Loader)

用于加载 JRE 的核心类库(c/c++代码实现),如:rt.jar、charsets.jar。加载 <JAVA_HOME>/lib 目录下的类。



扩展类加载器(Extension Class Loader)

加载 JRE 扩展类。加载 <JAVA_HOME>/lib/ext 目录下的类。



系统类加载器(Application Class Loader)

加载 classpath 路径下的类。



用户自定义加载器(User Defined Class Loader)

自定义类加载器,加载自定义路径下的类

类加载机制

全盘负责委托机制

当一个 ClassLoader 加载一个类的时候,除非显示的使用另一个 ClassLoader,该类锁依赖和引用的类也由这个 ClassLoader 加载。



双亲委派机制(父类加载机制)

当一个类加载器收到了类加载请求,会首先将该请求委托给父类加载器(非继承关系,逻辑上的父类)去完成,并且每一个层次类加载器都是如此,只有父类加载器无法完成加载请求时,子类加载器才尝试自己去加载。好处:

  • 避免用户自定义的包名覆盖系统的包功能

  • 避免类的重复加载



破坏双亲委派机制的场景

Tomcat 类加载器

Tomcat 是 web 容器,一个 web 容器可能要部署两个或者多个应用程序,不同的应用程序,可能会依赖同一个第三方类库的不同版本,因此要保证每一个应用程序的类库都是独立、相互隔离的;部署在同一个 web 容器中的相同类库的相同版本可以共享,否则,会有重复的类库被加载进 JVM;web 容器也有自己的类库,不能和应用程序的类库混淆,需要相互隔离;web 容器支持 jsp 文件修改后不用重启(热部署);



Tomcat 使用 Java 默认类加载器的问题

  1. 默认的类加载器无法加载两个相同类库的不同版本,类的全限定类名是一样的,所以无法解决相互隔离的问题

  2. 修改 jsp 文件后,因为类名一样,默认的类加载器不会重新加载,而是使用方法区中已经存在的类;所以需要每个 jsp 对应一个唯一的类加载器,当修改 jsp 的时候,直接卸载唯一的类加载器,然后重新创建类加载器,并加载 jsp 文件



Tomcat 的类加载器



CommonClassLoader:tomcat 最基本的类加载器,加载路径(/common/*)中的 class 可以被 tomcat 和各个 webapp 访问



CatalinaClassLoader:tomcat 私有的类加载器,webapp 不能访问其加载路径(/server/*)下的 class,即对 webapp 不可见



SharedClassLoader:各个 webapp 共享的类加载器,对 tomcat 不可见,加载路径:/shared/*



WebappClassLoader:webapp 私有的类加载器,只对当前 webapp 可见,加载路径:/WEB-INF/*



JspClassLoader:每一个 jsp 文件对应一个 JspClassLoader



默认情况下,conf 目录下的 catalina.properties 文件,没有指定 server.loader 以及 shared.loader,所以 tomcat 没有建立 CatalinaClassLoader 和 SharedClassLoader 的实例,这两个都会使用 CommonClassLoader 来代替。Tomcat6 之后,把 common、shared、server 目录合成了一个 lib 目录



Tomcat 的类加载机制

CommonClassLoader 能加载的类都可以被 Catalina ClassLoader 和 SharedClassLoader 使用,从而实现了公有类库的共用;

CatalinaClassLoader 和 Shared ClassLoader 加载的类则相互隔离;

WebAppClassLoader 可以使用 SharedClassLoader 加载到的类,但各个 WebAppClassLoader 加载的类则相互隔离;

JasperLoader 的加载范围仅仅是这个 Jsp 文件所编译出来的 .class 文件,目的就是为了被丢弃:当 Web 容器检测到 JSP 文件被修改时,会替换掉目前的 JasperLoader 的实例,并通过再建立一个新的 Jsp 类加载器来实现 Jsp 文件的 HotSwap 功能;

WebappClassLoader 和 jasperClassLoader 没有传给父类加载器去加载,先自己加载。加载不到时再走双亲委托。



可以通过在 Context.xml 文件中加上 <Loader delegate = "true"> 开启正统的“双亲委派”加载机制。






发布于: 2020 年 07 月 22 日 阅读数: 36
用户头像

Alex🐒

关注

还未添加个人签名 2020.04.30 加入

还未添加个人简介

评论

发布
暂无评论
JVM 类加载机制