什么是JVM类加载器?
类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。
一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。
类加载器负责读取 Java 字节代码,并转换成 java.lang.Class
类的一个实例。
每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()
方法就可以创建出该类的一个对象。
实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
类加载的过程
从类的生命周期而言,一个类包括如下阶段:
加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序进行,而解析阶段则不一定,它在某些情况下可能在初始化阶段后在开始,因为java支持运行时绑定。
加载过程:将.class文件通过IO流的方式加载到内存当中
- 1.将.class文件字节码内容加载到内存当中
- 2.先会将静态数据转换成方法区中的运行的数据结构
- 3.在堆内存当中生成一个代表这个类的Class对象,这个Class类的对象就是作为方法区数据访问的入口 Class.forName(com.wdksoft.User);
链接过程:
- 1.验证阶段:验证字节码文件的准确性,包含文件格式,元数据,符号引用,字节码等等
- 2.准备阶段:给类中的静态变量分配内存,并赋予初始值
- 3.解析阶段:将虚拟机常量池的符号引用替换成字节引用的过程
初始化过程:
- 初始化过程就会对类中的静态变量初始化为指定的值,执行静态代码块,执行构造器
类加载时机
加载(loading)阶段,java虚拟机规范中没有进行约束,但初始化阶段,java虚拟机严格规定了有且只有如下5种情况必须立即进行初始化(初始化前,必须经过加载、验证、准备阶段):
- (1)使用new实例化对象时,读取和设置类的静态变量、静态非字面值常量(静态字面值常量除外)时,调用静态方法时。
- (2)对内进行反射调用时。
- (3)当初始化一个类时,如果父类没有进行初始化,需要先初始化父类。
- (4)启动程序所使用的main方法所在类
- (5)当使用1.7的动态语音支持时。
如上5种场景又被称为主动引用,除此之外的引用称为被动引用,被动引用有如下3种常见情况
- 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
- 定义对象数组和集合,不会触发该类的初始化
- 类A引用类B的static final常量不会导致类B初始化(注意静态常量必须是字面值常量,否则还是会触发B的初始化)
注意:被动引用不会导致类初始化,但不代表类不会经历加载、验证、准备阶段。
类加载器的种类
Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:
- 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自
java.lang.ClassLoader
。 - 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
- 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过
ClassLoader.getSystemClassLoader()
来获取它。
除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader
类的方式实现自己的类加载器,以满足一些特殊的需求。
类加载器树状组织结构示意图
类加载方式
这里的类加载不是指类加载阶段,而是指整个类加载过程,即类加载阶段到初始化完成。
(1)隐式加载
- 创建类对象
- 使用类的静态域
- 创建子类对象
- 使用子类的静态域
- 在JVM启动时,BootStrapLoader会加载一些JVM自身运行所需的class
- 在JVM启动时,ExtClassLoader会加载指定目录下一些特殊的class
- 在JVM启动时,AppClassLoader会加载classpath路径下的class,以及main函数所在的类的class文件
(2)显式加载
- ClassLoader.loadClass(className),只加载和连接、不会进行初始化
- Class.forName(String name, boolean initialize,ClassLoader loader); 使用loader进行加载和连接,根据参数initialize决定是否初始化。
类加载机制
1.全盘负责委托机制
当进行类加载的时候,如果手动指定了ClassLoader,那么该类所依赖和引用的类也由这个类加载器进行加载
User->UserParent
指定User使用特定的类加载器,那么跟User类有依赖和引用关系的类也用这个类加载器进行加载
2.双亲委派机制
指先委托父类加载器寻找目标类,如果父类加载器无法进行类的加载则子类加载器自身处理
- 1.沙箱安全机制:自定义的String.class不会被加载,这样可以防止核心API库被随意篡改
- 2.避免类重复加载:当附加在其加载了该类是,就没有必要子类加载器也进行加载
3.如何破坏双亲委派机制
为什么要破坏双亲委派机制:父加载器需要委托子加载器在其进行加载
如何破坏:
- 1.重写ClassLoad类中的loadClass方法,指定加载哪一个类
- 2.手动调用系统类加载器Thread.currentThread().getContextClassLoader();
- 3.重写findClass
监控类加载过程
在当前启动类当中加入-verbose:class参数,启动则可以看到整个类加载的过程
代码
package com.wish; public class ClassLoaderTest { private static Integer i=50; static { System.out.println("静态代码块"); i=70; } public ClassLoaderTest(){ System.out.println(i); System.out.println("类中构造函数"); } public static void main(String[] args) { ClassLoaderTest test = new ClassLoaderTest(); } }
参数
结果
博客引用:https://blog.csdn.net/zhaocuit/article/details/93038538
https://www.ibm.com/developerworks/cn/java/j-lo-classloader/