类加载

类加载 - 图1

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。

类加载过程

  • 加载:加载二进制
  • 连接
    • 验证:验证类二进制的正确性
    • 准备
      • 静态变量分配内存(清空内存),初始化默认值 static a = 0;
      • 构造与该类相关联的方法表。
      • static final变量String或基本类型赋值在准备阶段就完成了】
    • 解析
      • 类中符号引用转为直接引用
      • ClassLoader.loadClass(className)执行到这里【默认不解析】,没有使用不进行初始化。
  • 初始化【class对象也是对象,对象就需要初始化】
      1. 静态变量赋值 static a = 3;
      1. 静态代码块。 (只初始化时执行一次,第二次new就没有了)
    • 非静态代码块,在创建对象的时候(即new一个对象的时候)执行,每次创建对象都会执行一次
    • Class.forName(className, true, this.getClass().getClassLoader())。注意第二个参数,是指Class被loading后是不是必须被初始化。

image.png
你知道Java中final和static修饰的变量是在什么时候赋值的吗?_Archie_java的博客-CSDN博客_final修饰的变量什么时候加载

构造函数是实例化阶段运行,即new
JVM内存结构

static变量赋值

JVM内存结构

主动使用

主动使用导致类的初始化

不会触发主动使用

  1. final static(String和基础变量int等)不会初始化,单static会初始化

parent.parentFinalStatic
Child.parentFinalStatic

  1. 单static谁的属性,初始化谁

Child.parentStatic,只初始化Parent,不会初始化Child。

  1. 创建数组不会初始化

Parent[] as = new Parent[10];

ClassLoader.loadClass 和 Class.forName() 有什么区别?

  • ClassLoader#loadClass(java.lang.String, boolean)
  1. protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException

第二个参数resolve ,是否进行解析。这是protected方法,默认不解析。

  • Class.forName()
    1. public static Class < ?>forName(String className) throws ClassNotFoundException {
    2. Class < ?>caller = Reflection.getCallerClass();
    3. return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    4. }
    类加载 - 图3
    forName0 第二个参数为true,表示是否进行初始化。
    可以传入指定的ClassLoader。

Class.forName(className)实际上是调用Class.forName(className, true, this.getClass().getClassLoader())。注意第二个参数,是指Class被loading后是不是必须被初始化。 ClassLoader.loadClass(className)实际上调用的是ClassLoader.loadClass(name, false) ,第二个参数指出Class是否被link。 java下Class.forName的作用是什么,为什么要使用它 - 青鸟飞鱼 - CSDN博客

结论:区别就是

  • Class.forName默认初始化
  • ClassLoader.loadClass默认不解析(没有符号引用转为直接饮用)

  • loadClass参数最多到“解析(Resolve)”阶段,forName可以通过参数进行类“初始化”。

  • loadClass不可以指定ClassLoader,forName可以指定ClassLoader,没有指定就用调用者的ClassLoader。

loadClass和Class.forName的区别 - 简书

jdbc驱动为什么用Class.forName(“com.mysql.jdbc.Driver”);,而不是放在classpath下自动加载?

Class.forName 不是为了加载类,
Class.forName 不是为了加载类,
Class.forName 不是为了加载类,而是为了执行Driver中的static 代码块。static代码块中将mysql的driver实现注册到了jdk中的DriverManager中。
类加载 - 图4
然后,DriverManager#getConnection(java.lang.String, java.util.Properties, java.lang.Class<?>)就会用到注册的driver
类加载 - 图5

  • JDBC学习2:为什么要写Class.forName(“XXX”)? - 五月的仓颉 - 博客园

    类加载器

    类加载 - 图6

    Jdk中的类加载器

    类加载 - 图7
    jdk中的classloader类。
    ExtClassLoader 和 AppClassLoader并不是继承关系。而是创建AppClassLoader时传递进来。
    image.png

    自定义ClassLoader加载顺序

    可以自定义ClassLoader重写loadClass 方法,
    通常我们重写的是findClass方法。
    loadClass先检查父classloader,然后调用findClass()方法。

但即便重写loadClass方法也不能加载自定义的java.lang.String类
因为针对java.*开头的类,jvm的实现中已经保证了必须由bootstrap来加载

SPI打破双亲委托机制?【不觉得,并没有自定义类加载器改变加载顺序】

为什么说java spi破坏双亲委派模型? - 知乎
父加载器能使用当前线程Thread.currentThread().getContextLoader() 所指定的ClassLoader来加载类【也就是父类(父加载器加载)的方法中使用子加载器加载类,通过Thread获取其ClassLoader】

DriverManager是通过BootstrapClassLoader加载的,在DriveManager中使用spi
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);

serviceLoader是bootstrap 类加载器加载,那通过serviceLoader加载出来的类是什么加载器加载的呢?
AppClassLoader
获取线程上下文ClassLoader
类加载 - 图9
spi中使用ClassLoader cl = Thread.currentThread().getContextClassLoader(); 获取当前线程的类加载器appClassLoader,来加载mysql包中的Driver类。

Tomcat ClassLoader 打破双亲委派机制?

Tomcat5 ClassLoader

类加载 - 图10

类加载 - 图11
image.png
tomcat7三个Classloader都是同一个实例。tomcat5是分开的。

Common ClassLoader

image.png

仅运行一个Tomcat实例时,这两个属性(catalina.home(安装目录),catalina.base(工作目录))指向的位置是相同的。 [Tomcat]使用Catalina_base部署多个tomcat实例 - 简书

Catalina也叫Server classloader。

Tomcat ClassLoader 加载顺序

tomcat5 到7 虽然ClassLoader改变,但加载顺序不变
默认的加载顺序是

  • Bootstrap classes of your JVM
  • /WEB-INF/classes of your web application
  • /WEB-INF/lib/*.jar of your web application
  • System class loader classes (described above)
  • Common class loader classes (described above)

如果设置了 则加载顺序是

  • Bootstrap classes of your JVM
  • System class loader classes (described above)
  • Common class loader classes (described above)
  • /WEB-INF/classes of your web application
  • /WEB-INF/lib/*.jar of your web application

Apache Tomcat 8 (8.0.53) - Class Loader HOW-TO
官方文档中的Bootstrap = Bootstrap + Extension

Tomcat ClassLoader违反双亲委派模型

在 CATALINA_HOME/lib 以及 WEB-INF/lib 中放置了 不同版本的jar包,此时就会导致 WEB-INF/lib中覆盖CATALINA_HOME/lib中的类。导致运行错误。
maven
不懂为什么要这么设计。
tomcat类加载器为什么要破坏双亲委派机制? - 牧云文仔 - 博客园
why does tomcat classloader break up parent delegation - Stack Overflow