一、类加载器子系统
- 类加载器子系统负责从文件系统或者网络中加载class文件,class文件开头有特定的文件标示(CA FE BA BE)。
- ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。
- 加载的类信息存放在方法区。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)。
1、加载阶段(loading)
- 1)、通过一个类的全限定名获取此类的二进制字节流
- 2)、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 3)、在内存中生成代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口
2、链接阶段(linking)
▪ 目的在于确保Class文件的字节流中包含信息符合当前虚拟机的要求,保证加载类的正确性,不会危害虚拟机自身安全
▪ 主要包括四种验证:文件格式,元数据,字节码,符号引用
▪ 为类变量分配内存并且设置该变量的默认初始值,零值。
▪ 不会初始final修饰的static,final在编译的时候就会分配。
▪ 不会为实例变量分配初始化,类变量会分配在方法区中,实例变量是随着对象一起分配到java堆中。
▪ 将常量池的符号引用转换为直接引用的过程。
▪ 符号引用就是一组符号来描述所引用的目标。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
▪ 事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行。
▪ 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。
3、初始化(initialization)
▪ 初始化阶段就是执行类构造器方法clinit方法的过程。
▪ 构造器方法中指令按语句在源文件中出现的顺序执行。
▪ clinit方法不定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。(如果不声明static变量和代码块,字节码文件中的method中就不会存在clinit的方法生成)
▪ 虚拟机必须保证一个类的clinit方法在多线程下被同步加锁。
二、类加载器的分类
JVM支持两种类型的类加载器:引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。
将所有派生于抽象类ClassLoader的类加载器都定义为自定义加载器。
不是上下层,也不是子父类关系,是等级关系
获取类加载器
public class ClassLoaderTest {
public static void main(String[] args) {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@1bf3d87
//获取其上层:扩展类加载器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@60991f
//获取其上上层类加载器
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println(bootstrapClassLoader);//null
//对于用户自定义类来说:默认使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@1bf3d87
//String类使用引导类加载器进行加载的。
ClassLoader stringClassLoader = String.class.getClassLoader();
System.out.println(stringClassLoader);//null
}
}
▪ 对于用户自定义类来说:默认使用系统类加载器进行加载
▪ 获取不到引导类加载器/启动类加载器(bootstrap ClassLoader)
▪ java的核心类库都是使用类加载器进行加载的
获取启动类加载器和扩展类加载器能够加载的api路径
public class GetClassLoader {
public static void main(String[] args) {
System.out.println("#########启动类加载器#########");
//获取BootstrapClassLoader能够加载的api路径
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
for(URL element:urLs){
System.out.println(element.toExternalForm());
}
System.out.println("#########启动类加载器#########");
String extDirs = System.getProperty("java.ext.dirs");
for(String path:extDirs.split(";")){
System.out.println(path);
}
}
}
运行结果:
虚拟机自带的加载器:
▪ 不能直接获取到(null)
▪ 这个类加载使用C/C++语言实现,嵌套在JVM内部。
▪ 用来加载java的核心库(见上图运行结果),用于提供JVM自身需要的类。
▪ 并不继承自java.lang.ClassLoader,没有父加载器。
▪ 加载扩展类和应用类加载器,并为他们的父类加载器。
▪ 只加载报名为java、javax、sun等开头的类。
▪ java编写,由sun.misc.Lancher$ExtClassLoader实现。
▪ 派生于ClassLoader类。
▪ 父类加载器为启动类加载器。
▪ 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。
▪ java语言编写,由sun.misc.Lancher$AppClassLoader实现。
▪ 派生于ClassLoader类。
▪ 父类加载器为扩展类加载器。
▪ 负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
▪ 该类加载器是程序中默认的类加载器,一般来说,java应用的类都是由它来完成加载。
▪ 通过ClassLoader#getSystemClassLoader()方法可以获取。
用户自定义类加载器:
- 为什么要自定义类加载器?
▪ 隔离加载类(在引入多个框架时,会出现引用的路径类似,发生冲突)
▪ 修改类加载的方法
▪ 扩展加载源
▪ 防止源码泄露 - 自定义类加载器的步骤:
▪ 1、继承抽象类java.lang.ClassLoader类
▪ 2、在findClass()中写自定义的类加载逻辑
▪ 3、如果没有过于复杂的要求,可以直接继承URLClassLoader类,这样就可以避免编写findClass()方法以及获取字节码流的方式。
三、双亲委派机制
Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时,才会将它的class文件加载到内存中生成class对象。加载某个类的class文件时,Java虚拟机采用的是双亲委派机制,即把请求交由给父类处理,是一种任务委派模式。
▪ 1)如果一个类加载器收到了类加载请求,它不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
▪ 2)如果父类加载器还存在父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
▪ 3)如果父类加载器可以完成类加载任务,就返回成功;若父类加载器无法完成此加载任务,子加载器才会尝试加载。这就是双亲委派模式。
▪ 避免类的重复加载。
▪ 保护程序安全,防止核心API被随意篡改。(例:自定义一个核心类java.lang.String/java.lang.xxx:会报错java.lang.SecurityException: Prohibited package name: java.lang
)
▪ 例子:自定义一个String类,但是在加载自定义String类的时候会率先使用引导类的加载器加载,而引导类加载器在加载的过程中会加载JDK自带的文件(rt.jar包中的java\lang\String.class),报错信息会说没有main方法,就是因为加载的是rt.jar包中的String类。这样就可以保证对java核心源代码的保护,这就是沙箱安全机制。
注意:
在JVM中标示两个class对象是否为同一个类存在两个必要条件:
- 1、类的完整类名必须一致,包括包名。
- 2、加载这个类的ClassLoader必须相同(指ClassLoader实例对象)。
类的主动使用和被动使用的区别:
- 类的被动使用不会导致类的初始化。
- 主动使用包括:
▪ 创建类的实例
▪ 访问某个类或接口的静态变量,或者对该静态变量赋值
▪ 调用类的静态方法
▪ 反射(比如:Class.forName(“com.xxxx.test”))
▪ 初始化一个类的子类
▪ Java虚拟机启动时被标明为启动类的类