类加载器的作用是如何找到指定的Class文件以及怎样将Class文件装载进内存
JVM流程内容大纲图
![](https://img.haomeiwen.com/i14766907/5ea075de2e789920.png)
类的生命周期
![](https://img.haomeiwen.com/i14766907/6d6c670ee2405913.png)
![](https://img.haomeiwen.com/i14766907/bcbecf27e6036380.png)
加载
![](https://img.haomeiwen.com/i14766907/c52a86aad31a7693.png)
![](https://img.haomeiwen.com/i14766907/b158db4a088a3dfe.png)
BootStrapClassLoader:根加载器
它是脱离 Java 语言,使用 C/C++ 编写的类加载器,所以当你尝试使用 ExtClassLoader 的实例调用 getParent() 方法获取其父加载器时会得到一个 null 值。
总之,对于 BootStrapClassLoader 这个根加载器我们需要知道三点:
根加载器使用 C/C++ 编写,我们无法在 Java 中获得其实例
根加载器默认加载系统变量 sun.boot.class.path 指定的类库
可以使用 -Xbootclasspath/a 参数追加根加载器的默认加载类库
ExtClassLoader:扩展类加载器
它是一个使用 Java 实现的类加载器(sun.misc.Launcher.ExtClassLoader),用于加载系统所需要的扩展类库。默认加载系统变量 java.ext.dirs 指定位置下的类库,通常是 $JRE_HOME/lib/ext 目录下的类库。
![](https://img.haomeiwen.com/i14766907/5dc7892e23a06cd8.png)
我们可以在启动时修改java.ext.dirs 变量的值来修改扩展类加载器的默认类库加载目录,但通常并不建议这样做。如果我们真的有需要扩展类加载器在启动时加载的类库,可以将其放置在默认的加载目录下。
总之,对于 ExtClassLoader 这个扩展类加载器我们需要知道两点:
扩展类加载器是使用 Java 实现的类加载器,我们可以在程序中获得它的实例并使用。
通常不建议修改java.ext.dirs 参数的值来修改默认加载目录,如有需要,可以将要加载的类库放到这个默认目录下。
AppClassLoader:应用类加载器
它和 ExtClassLoader 一样,也是使用 Java 实现的类加载器(sun.misc.Launcher.AppClassLoader)。它的作用是加载应用程序 classpath 下所有的类库。它是应用最广泛的类加载器,是我们最常打交道的类加载器,我们在程序中调用的很多 getClassLoader() 方法返回的都是它的实例。在我们自定义类加载器时如果没有特别指定,那么我们自定义的类加载器的默认父加载器也是这个应用类加载器。
总之,对于 AppClassLoader 这个应用类加载器我们需要知道三点:
应用类加载器是使用 Java 实现的类加载器,负责加载应用程序 classpath 下的类库。
应用类加载器是和我们最常打交道的类加载器。
没有特别指定的情况下,自定义类加载器的父加载器就是应用类加载器。
自定义类加载器:
除了上述三种 Java 默认提供的类加载器外,我们还可以通过继承 java.lang.ClassLoader 来自定义一个类加载器。如果在创建自定义类加载器时没有指定父加载器,那么默认使用 AppClassLoader 作为父加载器。
连接
2.1验证:确保被加载类的正确性,不会危害虚拟机自身的安全;
2.1.1文件格式验证:则是我们之前提到过的[类文件结构验证](https://blog.csdn.net/wolf_love666/article/details/86598763)
魔数、主次版本号、常量池的常量类型、指向常量的各种索引、UTF8编码、Class文件本身和各个部分是否缺失等等
2.1.2元数据验证(确保元数据的语义校验通过):
是否有父类(除Object)、这个类的父类是否继承了不允许被继承的类(被final修饰的类)、如果不是抽象类是否实现了父类或者接口要求的所有方法、类中的字段、方法是否与父类产生矛盾等
2.1.3字节码验证(通过数据流和控制流分析,确定程序语义是合法的、符合逻辑):
任意时候都能确保数据类型被正确使用,跳转指令不会跳转到方法体外的字节码指令上。保证方法体中类型转换是有效的。
2.1.4符号引用验证(确保解析动作能正常执行,无法通过的话则java.lang.IncompatibleClassChangeError的子类,如java.lang.IIIegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError等):
符号引用字符串描述的全限定名是否能找到对应的类,在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段。符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问
2.2准备:为类的静态变量分配内存,并将其初始化为默认值;
public static int value =123;
变量value在准备阶段过后的初始值为0而不是123,因为这个时候尚未开始执行任何java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器<clint>()方法中。所以把value赋值为123的动作将会在初始化阶段执行。
但是当final修饰的时候则准备阶段的时候就会为123.会为value生成ConstantValue属性。
2.3解析:把类中的符号引用(不一定在内存中)转换为直接引用(引用的目标必定在内存中存在);
在类结构文件中CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型
类或接口解析
字段解析
类方法解析
接口方法解析
初始化:什么时候需要对类进行初始化?
1)使用new该类实例化对象的时候;
2)读取或设置类静态字段的时候(但被final修饰的字段,在编译器时就被放入常量池的静态字段除外static final);
3)调用类静态方法的时候;
4)使用反射Class.forName(“xxxx”)对类进行反射调用的时候,该类需要初始化;
5) 初始化一个类的时候,有父类,先初始化父类(注:1. 接口除外,父接口在调用的时候才会被初始化;2.子类引用父类静态字 段,只会引发父类初始化);
6) 被标明为启动类的类(即包含main()方法的类)要初始化;
7)当使用JDK1.7的动态语言支持时,如果一个java.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、 REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
深入理解java虚拟机中提到有且只有这几种情况:
1--指令:new、读取或设置一个类的静态字段(被final修饰,已在编译期把结果放入常量池的静态字段除外)的时候,调用一个类的静态方法的时候。-------》都会触发字节码指令。
new/getstatic/putstatic/invokestatic
2--反射:java.lang.reflect包的方法对类进行反射调用
3--初始化某个类,如果有父类没初始化,先初始化父类
4--当虚拟机启动时候,用户需要指定执行的主类(包含main方法的那个类)虚拟机会先初始化这个主类。
5--当使用动态语言的时候java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
(简化版)理解类什么时候才被初始化:
3.1)创建类的实例,也就是new一个对象
3.2)访问某个类或接口的静态变量,或者对该静态变量赋值
3.3)调用类的静态方法
3.4)反射
3.5)初始化一个类的子类(会首先初始化子类的父类)
3.6)JVM启动时标明的启动类,即文件名和类名相同的那个类
只有这6中情况才会导致类的类的初始化。
双亲委托模式
![](https://img.haomeiwen.com/i14766907/9f5ab1f36ed442b7.png)
网友评论