JVM基础生命周期流程图
只有main()方法的java程序执行流程
classLoader.loadClass()的类加载流程(除引导类,所有类都一样)
- 加载:通过IO查找读取磁盘上的字节码文件,在调用到类才进行加载(调用类的方法,或者new 一个实例对象),会在内存创建一个Class对象,在方法区中代表这个类(用于获取该类的各种数据)。
- 验证:校验字节码文件是否符合规范。
- 准备:将类中的
非最终静态变量
分配内存地址,并赋予类型
默认值(根据类型给予,与实际赋值无关)。最终静态变量
会直接进行赋值操作,无需进行类型初始值赋予。 - 解析:将符号引用替换为直接引用,将一些静态方法(字符)替换为内存中的地址或句柄,即静态链接,而动态链接是指在程序运行期间将符号引用替换为直接引用。
- 初始化:对类的非最终静态变量初始化赋值(赋予
类中指定的初始值
,代替类型初始值
),并且执行静态代码块。
不会加载类的情况
注意:在未调用如何方法,未new对象的情况下。不会加载类,即只声明类如:
//不会加载类
User user=null;
//创建实例,会加载类
User user=new User();
//调用方法,会加载类
User.staticMethod();
能够获取到它的Class对象,就会加载类
不会进行初始化阶段的情况
在一些情况下类的加载不会进行初始化
- 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
- 定义对象数组,不会触发该类的初始化。(类都不加载)
- 常量在编译期间会存入调用类的常量池,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。(代论证)
- 通过类名获取Class对象,不会触发类的初始化。
- 通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化,该参数就是告诉JVM是否队类进行初始化。
- 通过ClassLoader默认的loadClass加载类,也不会触发初始化。
主要的类加载器
类的加载主要通过类加载器进行实现,而java包含的加载器如下:
- 引导类加载器(
实例sun.misc.Launcher
):由C++实现,用于加载支撑JVM运行的核心类库(Jre的lib目录下,如rt.jar、charsets.jar) - 扩展类加载器(
sun.misc.Launcher.ExtClassLoader
):由Java实现,用于加载支撑JVM运行的ext扩展目录中的jar包(Jre的lib目录下ext目录) - 应用程序类加载器(
sun.misc.Launcher.AppClassLoader
):由Java实现,用于加载ClassPath路径下的类包,也就是我们编程所写的类包。 - 自定义加载器:由Java实现,可以自己进行实现,常见的有Tomcat的加载器。
ExtClassLoader(扩展类加载器)在Launcher的构造方法中被设置为了AppClassLoader(应用类加载器)的父类加载器
而ExtClassLoader(扩展类加载器)的父类加载器由于是bootstrap(引导类加载器)而无法在Java中表示故被设置为null
另外bootstrap(引导类加载器)由C++进行加载,无需进行类加载
双亲委派机制
指类是如何判别在那个加载器中被加载
- 寻找是否加载阶段
- 进行加载阶段
实际上其原理就是在AppClassLoader.loadClass方法中进行执行的,主要流程:
- 检查该类是否已经被本
类加载器
加载,已加载直接返回(被加载的对象) - 诺未加载则判断是否存在父
类加载
器,存在即调用父类加载器
的loadClass方法,诺无父类加载器
(说明该加载器为引导类加载器),调用bootstrap类加载器
加载. - 诺到达bootstrap
类加载
器,并且bootstrap也未加载该类,则调用findClass方法尝试加载该类. - 诺加载到该类,直接返回。未加载该类则进行向下委派,由子
类加载器
调用findClass方法进行尝试加载。
注意:findClass方法不是类加载器的方法而是类加载器父类URLClassLoader的方法,其功能为在类加载器路径里查找并加载该类
- 双亲委派的作用
- 沙箱安全机制: 你自己写的同包名同类的java核心类时,双亲委派机制依旧会加载核心库中的类,而不会加载你所创建的类,从而保证核心类库不被随意篡改。
- 避免重复加载;当
子类加载器
加载过该类就无需向上委托。而当父类加载器
加载了该类直接返回,而子类加载器
无需加载,保证有且只有一个类加载器
加载了该类,并且该类只被加载了一次。保证被加载类的唯一性
全盘委托机制(全盘负责委托机制)
- 当ClassLoder(类加载器)加载一个类时,该类所依赖或引用的类也由该ClassLoder(类加载器)加载,除非指定使用另一个ClassLoder(类加载器)
实际上我学习的时候就有疑问,java核心类是最常被调用的类,那么如果每次都需要从应用类加载器从下到上找一次,会造成不必要的性能消耗。然而这个机制保证了不必要的消耗。
打破双亲委派机制
之所以要打破双亲委派机制,是为了实现不同的加载类的方式,例如Tomcat的war包就打破了双亲委派机制,从而实现运行不同的war包而不会互相干扰。
- 基本要求
- 自定义类加载器
- 自定义类加载器重写loadClass方法(
该方法中实现了双亲委派机制
)
注意:Tomcat类加载器打破了双亲加载机制,除无法加载的java核心类,都由Tomcat类加载器进行加载。
- webappClassLoader加载自己的目录下的class文件,不会传递给父
类加载器
,打破了双亲委派机制 - 因为打破双亲委派机制,即重写了findClass(),所以不会向父
加载器
寻找Class,即不会传递给父类。 - webappClassLoader重写了loadClass()方法,不再遵循双亲委派,而是自己加载所有类(不包括java核心类)