类加载机制概念

Java虚拟机把描述类的class文件加载到内存,对其进行校验、转换解析、初始化等操作,最终得到可以被虚拟机直接使用的java类型,这就是虚拟机的加载机制。

主要有五个步骤:

  1. 加载
    将class文件读入到内存中,并将其放在运行时数据区的方法区内,然后在堆中创建一个java.lang.Class对象,用来封装在方法区的数据结构。
    在这个阶段,主要完成如下三件事:

    1. 通过一个类的全限定名获取此类的二进制字节流(Class文件)。而获取的方式可以通过jar、war、zip、网络等方式
    2. 将字节流静态存储结构转换为方法区(永久代、元空间)的内部数据结构
    3. 在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据访问入口。其中Class对象没有规定是在堆内存中,它比较特殊,存在方法区中
  2. 验证:验证加载后的类格式、语义、字节码、符号引用(判断符号是否存在)

  3. 准备(分配内存空间):为类的静态变量在方法去分配内存并赋默认值(0或null)。其中静态常量在这个阶段就赋程序设定的值,比如static final int = 666;

  4. 解析:将类的二进制数据中的符号引用转为直接引用

  5. 类的初始化:把类加载到系统中,这个阶段才是真正执行java代码。主要工作是为静态变量赋程序设定的初值

双亲委派


关于双亲委派介绍网上资料很多,包括自定义类加载器、线程上下文类加载器等,这里就不做详细赘述了

线程上下文类加载器(双亲委派破坏者)

java中存在很多服务的提供者接口(Service Provider Interface,SPI),这些接口允许第三方为他们提供实现,然后进行载入。常见的SPI有JDBC、JNDI等,接口属于java的核心库,一般存储与rt.jar中,有Bootstrap加载器加载。

为什么需要线程上下文类加载器?

如果按照双亲委派的原则,我们该如何去加载到我们所实现的SPI呢,这就涉及到了线程上下文加载器(jdk 1.2开始引入),它是通过破坏双亲委派然后使Bootstrap加载器来反向委托线程上下文类加载器进行加载SPI实现类。

如何使用

初始化线程的上下文类加载器是系统类加载器(AppClassLoader),我们可以通过java.lang.Thread类中的getContextClassLoader()和setContextClassLoader(ClassLoader cl)方法进行获取或设置

源码解析

使用jdbc.jar为例来说明上下文类加载器是如何发现并加载实现类的

找到rt.jar中的java.sql.DriverManager类

public class DriverManager {...static {loadInitialDrivers();println("JDBC DriverManager initialized");}/*** 加载并初始化driver*/private static void loadInitialDrivers() {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {// 加载外部的实现类,如com.mysql.cj.jdbc.DriverServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);try{// 这里执行hasNext()会初进行加载while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}return null;}});
}

在DriveManager类初始化时执行了loadInitialDrivers()方法,然后通过ServiceLoader.load(Driver.class)去加载外部实现的驱动类,配置位置固定为:META-INF/services

接下来我们看下ServiceLoader.load(Driver.class)方法

    public static <S> ServiceLoader<S> load(Class<S> service) {// 获取上下文类加载器ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);}// load方法public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){// 调用静态方法load会实例化一个ServiceLoader对象return new ServiceLoader<>(service, loader);}private ServiceLoader(Class<S> svc, ClassLoader cl) {service = Objects.requireNonNull(svc, "Service interface cannot be null");// 如果没有定义上下文类加载器则使用默认的系统系统加载器loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;reload();}

在ServiceLoader构造中会判断类加载器并保存到变量loader中,我们来看下最终加载实现类的方法:

        private S nextService() {if (!hasNextService())throw new NoSuchElementException();String cn = nextName;nextName = null;Class<?> c = null;try {// 使用cn类加载器加载指定的servicec = Class.forName(cn, false, loader);} catch (ClassNotFoundException x) {fail(service,"Provider " + cn + " not found");}if (!service.isAssignableFrom(c)) {fail(service,"Provider " + cn  + " not a subtype");}try {// 类型转换S p = service.cast(c.newInstance());providers.put(cn, p);return p;} catch (Throwable x) {fail(service,"Provider " + cn + " could not be instantiated",x);}throw new Error();          // This cannot happen}

可以看到使用类的全限定名+loader加载了实现类com.mysql.cj.jdbc.Driver

加载META-INF/services的过程:

  1. 实现延迟服务提供者查找
    DriverManager.loadInitialDrivers --> ServiceL oader.load --> reload() --> lookuplterator = new Lazylterator(service, loader);
  2. 加载META-INF/services,初始化驱动
    loadedDrivers.iterator() --> driverslterator.hasNext() -->hasNextService --> ClassLoader.getSystemResources(fullName); --> driversIterator.next() --> nextService() --> Class.forName(fullName, false, loader)

Tomcat类加载器结构

参考《Tomcat架构解析》

Tomcat作为一个容器,需要解决下面几个问题:

  1. Java类库可以实现相互隔离:因为我们在一个tomcat中会部署多个web应用,他们可能依赖同一个第三方库,不能要求所有类都只有一份,需要保证他们各自的类库要可以独立使用,不相互影响
  2. Java类库可以相互共享:上一个隔离问题的延申,比如我们部署10个Spring应用,那么90%的类库是冗余的,这时我们希望把他们进行类库共享以节约资源
  3. 不受部署web应用程序的影响:部署的web有着不确定性,可能那一天就崩溃了,所以,基于安全考虑,容器所使用的类库应该与应用程序的类库相互独立

Tomcat类加载架构如下图:

它破坏了双亲委派,每个类加载器作用如下:

  1. Common:以System为父 类加载器,是位于Tomcat应用服务器顶层的公用类加载器。其路径为common.loader,默认指向$CATALINA_ HOME/ib下的包。
  2. Catalina:以Common为父加载器,是用于加载Tomcat应用服务器的类加载器,其路径为server.loader,默认为空。此时Tomcat使用Common类加载器加载应用服务器。
  3. Shared:以Common为父加载器,是所有Web应用的父加载器,其路径为shared.loader,默认为空。此时Tomcat使用Common类加载器作为Web应用的父加载器。
  4. Web应用:以Shared为父加载器,加载/WEB-INF/classes目录下的未压缩的Class和资源文件以及/WEB-INF/lib目录下的Jar包。如前所述,该类加载器只对当前Web应用可见,对其他Web应用均不可见。
  5. JSP:顾名思义,就是专门加载jsp文件的加载器

我们平时的web应用类加载器默认加载顺序为:

1. 先从缓存中加载
2. 如果没有,则从JVM的Bootstrap类加载器加载
3. 如果没有,则从当前类加载器加载(按照WEB-INF/classes、WEB-INF/lib的顺序)
4. 如果没有,则从父类加载器加载,加载顺序是AppClassLoader、Common、Shared

参考

  • 《Tomcat架构解析.刘光瑞》2.4节
  • 《Tomcat权威指南》

JVM类加载理解(线程上下文类加载器、Tomcat类加载器)相关推荐

  1. 真正理解线程上下文类加载器(多案例分析)

    1.线程上下文类加载器是从jdk1.2开始引入的,类Thread中的getContextClassLoader()与setContextClassLoader(ClassLoader c1),分别用来 ...

  2. 类加载机制:双亲委任模型和tomcat类加载器

    简介 类是如何加载的,那么必须要面对的几个问题如下 什么是类加载机制? 什么是双亲委任模型? 如何破坏双亲委任模型? Tomcat 的类加载器是怎么设计的? 类加载机制 Java 中的类加载器大致可以 ...

  3. 聊聊JVM(五)从JVM角度理解线程

    这篇说说如何从JVM的角度来理解线程,可以对Java的线程模型有一个更加深入的理解,对GC的一些细节也会理解地更加深刻.本文基于HotSpot的OpenJDK7实现. 我们知道JVM主要是用C++实现 ...

  4. 接口多个实现类加载哪个_深入理解JVM虚拟机7:JNDI,OSGI,Tomcat类加载器实现

    本文转自互联网,侵删 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutori ...

  5. (二)JVM成神路之剖析Java类加载子系统、双亲委派机制及线程上下文类加载器

    引言 上篇<初识Java虚拟机>文章中曾提及到:我们所编写的Java代码经过编译之后,会生成对应的class字节码文件,而在程序启动时会通过类加载子系统将这些字节码文件先装载进内存,然后再 ...

  6. jdbc驱动类加载直接指定线程上下文加载器加载

    jdbc获取连接是这么写的,看下代码都干了什么,是如何把驱动类加载到jvm的. Class.forName是装载一个类并且对其进行实例化的操作.这里mysql驱动com.sql.Driver的静态方法 ...

  7. 利用classloader同一个项目中加载另一个同名的类_线程上下文类加载器ContextClassLoader内存泄漏隐患...

    前提 今天(2020-01-18)在编写Netty相关代码的时候,从Netty源码中的ThreadDeathWatcher和GlobalEventExecutor追溯到两个和线程上下文类加载器Cont ...

  8. 类加载器-线程上下文

    线程上下文类加载器 我们在使用 JDBC 时,都需要加载 Driver 驱动,不知道你注意到没有,不写 Class.forName("com.mysql.jdbc.Driver") ...

  9. java查看上下文加载器_线程上下文类加载器

    package util.tom; import java.io.*; public class ThreadClassLoader extends Thread { @Override public ...

最新文章

  1. Runtime知识点整理
  2. 防Xss攻击,包含富文本编辑器的处理
  3. spring boot: Bean的初始化和销毁 (一般注入说明(三) AnnotationConfigApplicationContext容器 JSR250注解)...
  4. 碗都交出去了,能不能分到羹?
  5. VTK:Utilities之Scalars
  6. 如何获取Google地图API密钥?(翻译版)
  7. python二叉树的创建与遍历
  8. MySQL 基础 ———— 分组查询
  9. python 解方程 sympy_Python数据处理篇之Sympy系列(五)---解方程
  10. mysql parameters_MySqlCommand Command.Parameters.Add已过时?mysql-问答-阿里云开发者社区-阿里云...
  11. Linux tcp同时多个连接,我的linux tcp server最多只能同时进行10个TCP请求,其余的都在等待了,如何让所有的连接都同时进行?...
  12. Emacs+Lisp环境搭建
  13. matlab中单刀双掷开关,proteus 怎样找单刀双掷开关
  14. 基于JavaEE的开放平台出租车系统_JSP网站设计_MySQL数据库设计
  15. MSCap: Multi-Style Image Captioning with Unpaired Stylized Text
  16. html css animate,animate.css的使用方法
  17. 进一步的飞鸽传书官方网站消息
  18. 人工智能驾驶奥运会 Duckietown AI Driving Olympics
  19. python中rim的用法_词汇精选:rim的用法和辨析
  20. 大学英语计算机统考机考,大学英语四级考试机考

热门文章

  1. Android小知识-了解下Android系统的显示原理
  2. 跟着内核学框架-从misc子系统到3+2+1设备识别驱动框架
  3. nginx查看配置文件nginx.conf路径
  4. 【转】第7篇:Xilium CefGlue 关于 CLR Object 与 JS 交互类库封装报告:全自动注册与反射方法分析...
  5. 将EnyimMemcached从.NET Core RC1升级至RC2
  6. GDC2016 【巫师3 狂猎】的游戏事件工作流
  7. (转)百度Map API
  8. Json序列化空时间字段出异常
  9. netbsd配置gnome桌面
  10. 20172318 2018-2019-1 《程序设计与数据结构》第9周学习总结