【JVM】Java类的加载流程以及双亲委派,全盘托管,以及如何打破双亲委派机制

JVM基础生命周期流程图

【JVM】Java类的加载流程以及双亲委派,全盘托管,以及如何打破双亲委派机制_第1张图片

只有main()方法的java程序执行流程

classLoader.loadClass()的类加载流程(除引导类,所有类都一样)

  1. 加载:通过IO查找读取磁盘上的字节码文件,在调用到类才进行加载(调用类的方法,或者new 一个实例对象),会在内存创建一个Class对象,在方法区中代表这个类(用于获取该类的各种数据)。
  2. 验证:校验字节码文件是否符合规范。
  3. 准备:将类中的非最终静态变量分配内存地址,并赋予类型默认值(根据类型给予,与实际赋值无关)。最终静态变量会直接进行赋值操作,无需进行类型初始值赋予。
  4. 解析:将符号引用替换为直接引用,将一些静态方法(字符)替换为内存中的地址或句柄,即静态链接,而动态链接是指在程序运行期间将符号引用替换为直接引用。
  5. 初始化:对类的非最终静态变量初始化赋值(赋予类中指定的初始值,代替类型初始值),并且执行静态代码块。

不会加载类的情况

注意:在未调用如何方法,未new对象的情况下。不会加载类,即只声明类如:

//不会加载类
User user=null;
//创建实例,会加载类
User user=new User();
//调用方法,会加载类
User.staticMethod();

能够获取到它的Class对象,就会加载类

不会进行初始化阶段的情况

在一些情况下类的加载不会进行初始化

  1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
  2. 定义对象数组,不会触发该类的初始化。(类都不加载)
  3. 常量在编译期间会存入调用类的常量池,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。(代论证)
  4. 通过类名获取Class对象,不会触发类的初始化。
  5. 通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化,该参数就是告诉JVM是否队类进行初始化。
  6. 通过ClassLoader默认的loadClass加载类,也不会触发初始化。

主要的类加载器

类的加载主要通过类加载器进行实现,而java包含的加载器如下:

  1. 引导类加载器(实例sun.misc.Launcher):由C++实现,用于加载支撑JVM运行的核心类库(Jre的lib目录下,如rt.jar、charsets.jar)
  2. 扩展类加载器(sun.misc.Launcher.ExtClassLoader):由Java实现,用于加载支撑JVM运行的ext扩展目录中的jar包(Jre的lib目录下ext目录)
  3. 应用程序类加载器(sun.misc.Launcher.AppClassLoader):由Java实现,用于加载ClassPath路径下的类包,也就是我们编程所写的类包。
  4. 自定义加载器:由Java实现,可以自己进行实现,常见的有Tomcat的加载器。
Launcher构造方法
父类加载器
应用类加载器
扩展类加载器

ExtClassLoader(扩展类加载器)在Launcher的构造方法中被设置为了AppClassLoader(应用类加载器)的父类加载器
而ExtClassLoader(扩展类加载器)的父类加载器由于是bootstrap(引导类加载器)而无法在Java中表示故被设置为null
另外bootstrap(引导类加载器)由C++进行加载,无需进行类加载

双亲委派机制

指类是如何判别在那个加载器中被加载

  • 寻找是否加载阶段
检查是否已加载,否则向上委托父加载器加载
向上委托
向上委托
引导类加载器
应用加载器
扩展类加载器
  • 进行加载阶段
父加载器加载失败由子加载器进行加载
向下委派
向下委派
应用加载器
引导类加载器
扩展类加载器

实际上其原理就是在AppClassLoader.loadClass方法中进行执行的,主要流程:

  1. 检查该类是否已经被本类加载器加载,已加载直接返回(被加载的对象)
  2. 诺未加载则判断是否存在父类加载器,存在即调用父类加载器的loadClass方法,诺无父类加载器(说明该加载器为引导类加载器),调用bootstrap类加载器加载.
  3. 诺到达bootstrap类加载器,并且bootstrap也未加载该类,则调用findClass方法尝试加载该类.
  4. 诺加载到该类,直接返回。未加载该类则进行向下委派,由子类加载器调用findClass方法进行尝试加载。

注意:findClass方法不是类加载器的方法而是类加载器父类URLClassLoader的方法,其功能为在类加载器路径里查找并加载该类

  • 双亲委派的作用
    1. 沙箱安全机制: 你自己写的同包名同类的java核心类时,双亲委派机制依旧会加载核心库中的类,而不会加载你所创建的类,从而保证核心类库不被随意篡改。
    2. 避免重复加载;当子类加载器加载过该类就无需向上委托。而当父类加载器加载了该类直接返回,而子类加载器无需加载,保证有且只有一个类加载器加载了该类,并且该类只被加载了一次。保证被加载类的唯一性

全盘委托机制(全盘负责委托机制)

  • 当ClassLoder(类加载器)加载一个类时,该类所依赖或引用的类也由该ClassLoder(类加载器)加载,除非指定使用另一个ClassLoder(类加载器)

实际上我学习的时候就有疑问,java核心类是最常被调用的类,那么如果每次都需要从应用类加载器从下到上找一次,会造成不必要的性能消耗。然而这个机制保证了不必要的消耗。

打破双亲委派机制

之所以要打破双亲委派机制,是为了实现不同的加载类的方式,例如Tomcat的war包就打破了双亲委派机制,从而实现运行不同的war包而不会互相干扰。

  • 基本要求
    • 自定义类加载器
    • 自定义类加载器重写loadClass方法(该方法中实现了双亲委派机制)
...
应用类加载器
Tomcat类加载器(加载Tomcat的lib)
Tomcat类加载器<加载War1>WebappClassLoader
Tomcat类加载器<加载War2>WebappClassLoader

注意:Tomcat类加载器打破了双亲加载机制,除无法加载的java核心类,都由Tomcat类加载器进行加载。

  • webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器,打破了双亲委派机制
  • 因为打破双亲委派机制,即重写了findClass(),所以不会向父加载器寻找Class,即不会传递给父类。
  • webappClassLoader重写了loadClass()方法,不再遵循双亲委派,而是自己加载所有类(不包括java核心类)

你可能感兴趣的:(JVM,JVM,双亲委派,全盘托管,类加载器)