博客主页:@风一样的美狼子
欢迎关注:点赞收藏留言
系列专栏: 《云平台实战》、《Linux随你玩-实操》
在阳光下灿烂,风雨中奔跑,泪水中成长,拼搏中展望。
一起加油,去追寻、去成为更好的自己!

文章目录

  • 前言
  • 1、类
    • 1.1、类装载的执行过程
    • 1.2、类加载器种类以及加载范围
      • 1.2.1、启动类加载器(Bootstrap ClassLoader)
      • 1.2.2、扩展类加载器(Extension ClassLoader)
      • 1.2.3、应用程序类加载器(Application ClassLoader)
      • 1.2.4、自定义类加载器(User ClassLoader)
      • 1.2.5、模块化中的类加载器
        • 1.2.5.1、JDK9中类加载器的变化
        • 1.2.5.2、OSGi模块化服务
    • 1.3、 双亲委派是什么
    • 1.4、为什么需要有双亲委派呢
    • 1.5、既然需要有双亲委派为何还要破坏双亲委派模型呢
    • 1.6、如何破坏双亲委派模型
    • 1.7、如何自定义一个类加载器
    • 1.8、 热部署原理
    • 1.9、结语

前言

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,那我们使用的类是如何进入JVM 这个神秘世界的了?它在里面都发生了什么?今天我就带领大家揭晓它的 神秘面纱,GO!

1、类

1.1、类装载的执行过程

类装载分为以下 5 个步骤:

  1. 加载: 根据查找路径找到相应的 class 文件中的二进制数据读入到内存中,并为之创建一个java.lang.Class对象; 通过使用不同的类加载器可以从不同来源加载类的二进制数据,
    通常有如下几种来源:
    a.从本地文件系统加载
    b.从jar包加载
    c.通过网络加载
    d.把一个Java源文件动态编译,并执行加载
  2. 检查: 检查加载的 class 文件的正确性;(文件格式验证、元数据验证、字节码验证、符号引用验证)
  3. 准备: 给类中的静态变量分配内存空间,并设置默认初始值;
  4. 解析: 虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,> >而在直接引用直接指向内存中的地址;
  5. 初始化: 对静态变量和静态代码块执行初始化工作。

类其实还有其它过程:

使用: 对象都出来了,业务系统直接调用阶段。
卸载: 用完了,可以被GC回收了。

1.2、类加载器种类以及加载范围

类加载器负责加载所有的类,其为所有被载入内存的类生成一个java.lang.Class实例对象。

1.2.1、启动类加载器(Bootstrap ClassLoader)

最顶层类加载器,该类没有父加载器,用来加载Java的核心类,启动类加载器的实现依赖于底层操作系统,属于虚拟机实现的一部分,它并不继承自java.lang.classLoader。
负责加载jvm的核心类库,比如java.lang.*等,从系统属性中的sun.boot.class.path所指定的目录中加载类库。他的具体实现由Java虚拟机底层C++代码实现。

1.2.2、扩展类加载器(Extension ClassLoader)

它的父类为启动类加载器,扩展类加载器是纯java类,是ClassLoader类的子类,负责加载JRE的扩展目录。

从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库,如果把用户的jar文件放在这个目录下,也会自动由扩展类加载器加载。继承java.lang.ClassLoader。

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

它的父类为扩展类加载器,它从环境变量classpath或者系统属性java.lang.path所指定的目录中加载类,继承自java.lang.ClassLoader。

1.2.4、自定义类加载器(User ClassLoader)

除了上面三个自带的以外,用户还能制定自己的类加载器,但是所有自定义的类加载器都应该继承自java.lang.ClassLoader。比如热部署、tomcat都会用到自定义类加载器。

1.2.5、模块化中的类加载器

JDK9开始引入模块化,是为了能够实现模块化的“可配置封装隔离机制”
某个类库到底是在模块还是在传统的jar包,只取决于他存放在哪种路径上
在这之前,如果类路径中确实了运行时依赖的类型,那就只能等程序运行到发生该类型的加载,连接时才会报运行异常。
在JDK9之后,如果启用了模块化进行封装,模块就可以声明对其他模块的显式依赖,这样JVM就能够在启动时验证应用程序的完备性。

1.2.5.1、JDK9中类加载器的变化

1、扩展类加载器(Extension Class Loader)被平台类加载器(Platform Class Loader)取代。
2、Java类库不再保留\lib\ext,JDK已基于模块化进行构建(原来的rt.jar和tools.jar被拆分成数十个JMOD)。
3、取消了\jre目录,因为随时可以组合构建出程序运行所需的jre,如我们只需要使用java.base模型中的类型,那么随时可以打包出一个jre,需要如下命令:
jlink -p $JAVA_HOME/jmods –add-modules java.base –output jre
4、平台类加载器(Platform Class Loader)和应用类加载器(Application Class
Loader)都不再派生自java.net.URLClassLoader,而全部继承jdk.internal.loader.BuiltinClassLoader。
5、当平台及应用程序类加载器收到类加载请求,在委派给父加载器加载前,先判断该类是否能够归属到某个系统模块中,如果可以找到归属系统模块,就优先委派给负责那个模块的加载器加载。
因此模块化中的类加载委派关系如下:(与三层类加载器图对比)

1.2.5.2、OSGi模块化服务

在JDK9之后OSGi的热部署成为当下流行的一项优势。
它通过自定义类加载机制实现,每一个程序模块(Bundle)都有一个属于自己的类加载器,当需要更换一个Bundle时,就把Bundle联通类加载器一起换掉,以实现热替换
在此环境下,类加载器不再需要双亲委派模型的树状结构,而是进一步发展为更加复杂的网状结构。

1.3、 双亲委派是什么

如果一个类加载器收到了类加载的请求,他首先会从自己缓存里查找是否之前加载过这个class,加载过直接返回,没加载过的话他会把这个请求委派给父类加载器去完成,每一层都是如此,类似递归,一直递归到顶层父类。
也就是Bootstrap ClassLoader,只要加载完成就会返回结果,如果顶层父类加载器无法加载此class,则会返回去交给子类加载器去尝试加载,若最底层的子类加载器也没找到,则会抛出ClassNotFoundException。

1.4、为什么需要有双亲委派呢

主要还是防止内存中出现多份同样的字节码,安全

比如自己重写个java.lang.Object并放到Classpath中,没有双亲委派的话直接自己执行了,那不安全。双亲委派可以保证这个类只能被顶层Bootstrap Classloader类加载器加载,从而确保只有JVM中有且仅有一份正常的java核心类。如果有多个的话,那么就乱套了。比如相同的类instance
of可能返回false,因为可能父类不是同一个类加载器加载的Object。

1.5、既然需要有双亲委派为何还要破坏双亲委派模型呢

Jdbc为什么要破坏双亲委派模型?
以前的用法是未破坏双亲委派模型的,比如Class.forName(“com.mysql.cj.jdbc.Driver”);

而在JDBC4.0以后,开始支持使用spi的方式来注册这个Driver,具体做法就是在mysql的jar包中的META-INF/services/java.sql.Driver文件中指明当前使用的Driver是哪个,然后使用的时候就不需要我们手动的去加载驱动了,我们只需要直接获取连接就可以了。
Connection con = DriverManager.getConnection(url, username, password);

为什么JDBC需要破坏双亲委派模式呢?
**原因是原生的JDBC中Driver驱动本身只是一个接口,并没有具体的实现,具体的实现是由不同数据库类型去实现的。**例如,MySQL的mysql-connector-*.jar中的Driver类具体实现的。

原生的JDBC中的类是放在rt.jar包的,是由Bootstrap加载器进行类加载的,在JDBC中的Driver类中需要动态去加载不同数据库类型的Driver类,而mysql-connector-*.jar中的Driver类是用户自己写的代码,那Bootstrap类加载器肯定是不能进行加载的,既然是自己编写的代码,那就需要由Application类加载器去进行类加载。
这个时候就引入线程上下文件类加载器(Thread Context ClassLoader),通过这个东西程序就可以把原本需要由Bootstrap类加载器进行加载的类由Application类加载器去进行加载了。

Tomcat为什么要破坏双亲委派模型?
因为一个Tomcat可以部署N个web应用,但是每个web应用都有自己的classloader,互不干扰

如web1里面有com.test.A.class,web2里面也有com.test.A.class,如果没打破双亲委派模型的话,那么web1加载完后,web2在加载的话会冲突。
因为只有一套classloader,却出现了两个重复的类路径,所以tomcat打破了,他是线程级别的,不同web应用是不同的classloader。
Java spi 方式,比如jdbc4.0开始就是其中之一。
热部署的场景会破坏,否则实现不了热部署。

1.6、如何破坏双亲委派模型

重写loadClass方法,别重写findClass方法,因为loadClass是核心入口,将其重写成自定义逻辑即可破坏双亲委派模型。

1.7、如何自定义一个类加载器

只需要继承java.lang.Classloader类,然后覆盖他的findClass(String name)方法即可,该方法根据参数指定的类名称,返回对应 的Class对象的引用。

1.8、 热部署原理

采取破坏双亲委派模型的手段来实现热部署,默认的loadClass()方法先找缓存,你改了class字节码也不会热加载,所以自定义ClassLoader,去掉找缓存那部分,直接就去加载,也就是每次都重新加载。

1.9、结语

各位看官今天就到此为止,此系列简要笔记后续会不断更新, 欢迎关注:点赞收藏留言!
在这里插入图片描述