文章目录
一、类加载器子系统作用
- java编译后生成的.class文件(字节码文件),需要在JVM的类加载子系统处理下加载到内存中,能被加载的字节码文件都是以CA FE BA BE开头。
- 类加载子系统只负责字节码文件的加载,加载后是否能正常的执行,需要JVM另一个结构执行引擎决定。
- 经过加载后的字节码文件对应到内存中,生成大的Class实例,同时对静态变量等做初始化。
二、类加载过程
类加载过程总体分为Loading(加载)、Linking(链接)、Initialization(初始化)三个环节,在Linking阶段又细分为Verification(验证)、Preparation(准备)、Resolution(解析)三个环节。
1、加载(Loading)
- 通过类的全限定名获取定义此类的二进制字节流
- 将这个类所代表的静态存储结构转化为方法区的运行时数据结构
- 在堆中生成一个代表这个类的java.lang.Class实例对象,作为方法区这个类的各种数据访问入口
2、验证(Verification)
验证字节码文件的字节流中包含信息符合虚拟机规范,以防止不正常的字节流危害虚拟机安全。所有能被JVM识别的字节码文件,它的有效起始都是CA FE BA BE,它是JVM识别的一个标识。
3、准备(Preparation)
-
为静态变量分配内存并且赋默认初始值,不同类型初始值不同
数据类型 零值 int 0 long 0L short (short)0 char ‘\u0000’ byte (byte)0 boolean false float 0.0f double 0.0d reference null -
如果 static 变量是 final 的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
-
如果 static 变量是 final 的引用类型,即new的对象,赋值在初始化阶段完成
4、解析(Resolution)
解析阶段特点现在看不是太理解,可能需要学了JVM其它结构才懂。。。
- 将常量池内的符号应用转换为直接引用。
- 符号引用是一组来描述所引用的目标。直接引用就是直接指向目标的指针、相对偏移量或者一个间接定位到目标的句柄。
- 解析操作往往会伴随着JVM在执行完初始化之后在执行。
- 解析操作主要针对类的接口、字段、类方法、接口方法、方法类型等。
5、初始化(Initialization)
在字节码文件中,一个类的方法会被解析到methods文件目录下,构造器对应目录下的<init>()方法, main()就对应main()。
-
初始化阶段就是执行类构造方法<clinit>()的过程。这个方法不需要定义,它是由编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并来的。当一个类没有类变量的赋值动作和静态代码块,那字节码文件中就不存在<clinit>()方法,反之存在。
-
构造器方法中的指令按照语句在源文件中出现的顺序执行。
-
<clinit>()不同于类的构造器,类的构造器在虚拟机视角下是<init>()方法。
-
如果该类具有父类,JVM在保证子类的<clinit>()执行之前,父类的<clinit>()方法已经执行完毕。
-
JVM必须保证一个类的<clinit>()方法在多线程下被同步加锁。
三、类加载器
1、类加载器分类
JVM支持两种类型类加载器,分别为引导类加载器和自定义类加载器。
2、类加载器特点
-
JVM规范里面规定,只要派生自抽象类ClassLoader的类加载器都叫自定义类加载器;ExtClassLoader就是扩展类加载器,AppClassLoader是系统类加载器。
-
四种类加载器不是继承关系,而是包含关系。引导类加载器是存在于系统内部,由c++编写的;其它几种类型类加载器都可以获取到父类加载器,由上面包含关系可以看出,扩展类加载器父加载器是引导类加载器,系统类加载器父类是扩展类加载器,而用户自己编写类的是通过系统类加载器加载。可以通过getParent()方法获取该类加载器的父加载器。
public class ClassLoaderDemo2 { public static void main(String[] args) { // 对于用户自己定义的类,默认使用系统类加载器加载。 ClassLoader appClassLoader = ClassLoaderDemo2.class.getClassLoader(); System.out.println("print: " + appClassLoader); // print: sun.misc.Launcher$AppClassLoader@18b4aac2 // 对于系统类加载器,通过getParent()方法获取其父加载器 ClassLoader appClassLoaderParent = appClassLoader.getParent(); System.out.println("print: " + appClassLoaderParent); // print: sun.misc.Launcher$ExtClassLoader@1b6d3586 // 对于扩展类加载器,通过getParent()获取不到其父加载器,因为引导类加载器不是用java语言实现的 ClassLoader extClassLoader = appClassLoaderParent.getParent(); System.out.println("print: " + extClassLoader); // pint: null } }
-
不同类加载器加载不同目录下的文件
-
引导类加载器加载以下目录:
-
扩展类加载器加载以下目录:
-
系统类加载器加载目录:环境变量classpath或者系统属性java.class,path指定目录下的类库
-
-
类的加载一般依靠引导类、扩展类、系统类相互配合执行,在特定场合下,开发人员可以自己定义类的加载器。自己定义类的加载器需要继承抽象类ClassLoader,重写findClass()方法;或者直接继承URLClassLoader。
四、双亲委派机制
JVM对字节码文件采取按需加载,就是只有在使用到这个类的时候才会加载,生成class对象放到堆中。加载某个类的class文件时,JVM采取双亲委派机制,就是把加载请求向上委派父类处理,一直到引导类加载器;如果父类加载器无法加载,会将加载请求向下分配给子类。
具体加载机制:
- 一个类加载器收到加载请求,他不会先去加载,而是先向上委托父类加载器加载;
- 父类加载器还存在父类,继续向上委托直到引导类加载器;
- 引导类加载器可以加载,就成功返回;若无法加载,就向下分配给扩展类加载器;
- 扩展类加载器可以加载,成功返回;若无法加载,向下分配给系统类加载器。