1.类加载子系统
类加载子系统主要做的就是从本地磁盘或者网络上读取.class字节码文件,将字节码文件转换存放到内存中
类加载子系统主要分为三个部分
1.1 加载阶段
- 通过类名获取此类的二进制字节流
- 将这个字节流的静态结构转换为方法区的运行时数据结构
- 在内存中生成Class对象(java.lang.Class) 作为方法区这个类的各种数据访问入口
1.2 链接
- 验证 验证字节码文件是否符合当前虚拟机的规范 还有其他验证
- 准备 给变量赋初始值 此处赋值并不包含常量,常量在编译的时候就已经初始化了
- 解析 把常量池中的符号引用转换为直接引用 例如java.lang,Object等类
1.3 初始化
把类中的对类变量的赋值操作操作和静态代码块中的赋值操作合并生成一个方法 clinit()方法,在这个方法中对变量进行赋值操作
仅限于静态变量,非静态变量是随着类的实例化才创建的
也即clinit只有类有静态变量的时候才会出现
1.4 类加载器
分为两种类型 引导类加载器 和自定义类加载器
jvm规范中指出: 只要是继承了ClassLoader的类加载器都是自定义加载器
也就是说,除了引导类加载器(BootstrapClassLoader) 之外的类加载器都是自定义类加载器
扩展类加载器(ExtensionClassLoader) 和系统类加载器(SystemClassLoader)也属于自定义类加载器
这四类是包含关系,不是上层下层关系,也不是继承关系
测试下加载器
package com.zy.study02;
/**
* @Author: Zy
* @Date: 2021/7/15 8:43
*/
public class ClassLoaderTest {
public static void main(String[] args) {
// 系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
// 从系统类加载器获取上层加载器 扩展类加载器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);
// 试图使用扩展类加载器获取上层加载器 引导类加载器
// 获取不到
ClassLoader bootStrapClassLoader = extClassLoader.getParent();
System.out.println(bootStrapClassLoader);
// 打印结果
//sun.misc.Launcher$AppClassLoader@18b4aac2 系统类加载器
//sun.misc.Launcher$ExtClassLoader@4554617c 扩展类加载器
//null 引导类加载器 无法获取
// 测试当前类(即用户自定义类)是使用什么类加载器加载的
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
// sun.misc.Launcher$AppClassLoader@18b4aac2
// 可以看出是系统类加载器 并且是单例的
// 测试java核心类库使用什么类加载器 以String为例
ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println(classLoader1);
// null
// 可以看出java核心类库使用的是引导类加载器
}
}
2.4.1 bootStrapClassLoader 引导类加载器
- 是由c/c++编写
- 加载Java的核心类库(rt.jar,resource.jar等)
- 没有父加载器
- 是扩展类加载器和系统类加载器的父加载器
- 只加载包名为 java,javax,sun的包(安全)
1.4.2 ExtensionClassLoader 扩展类加载器
- java编写
- 派生于BootStrapClassLoader
- 从java.ext.dirs系统属性指定的目录加载类库,或者从jdk
1.4.3 AppClassLoader 系统类加载器
- java编写 由sun.misc.Launcher$AppClassLoader实现
- 派生于ClassLoader类 间接继承于ClassLoader
- 上层加载器为扩展类加载器
- 加载环境变量classpath下的类库
- java应用的中的类都由系统类加载器加载
- 通过ClassLoader.getSystemClassLoader()获取
1.4.4 User-Defined ClassLoader 用户自定义加载器
作用:
- 隔离加载类 防止类冲突
- 修改类加载的方式 动态加载类
- 扩展加载源 可以从数据库等地方获取字节码文件进行加载,扩展加载类的来源
- 防止源码泄露 可以对字节码文件进行加密,然后在自定义加载器中进行解密
自定义类加载器的步骤
jdk1.2之前重写ClassLoader的loadClass方法
jdk1.2之后重写ClassLoader的findClass方法
如果是不太复杂的逻辑 也可以直接继承UrlClassLoader,不用再写获取二进制字节码文件的方法
import java.net.URL;
import java.net.URLClassLoader;
* @Date: 2021/7/16 11:02
public class UserDefinedClassLoaderTest extends URLClassLoader {
public UserDefinedClassLoaderTest(URL[] urls) {
// 自定义类加载器的逻辑
super(urls);
1.4.5 ClassLoader 类加载器
所有类加载器的父类(不包含BootStrapClassLoader(C++编写的类加载器))
获取ClassLoader的方式:
- clazz.getClassLoader()
- Thread.currentThread().getContextClassLoader()
- ClassLoader.getSystemClassLoader().getParent()
* @Date: 2021/7/16 11:33
public class ClassLoaderGet {
try {
// 通过clazz.getClassLoader()方法获取引导类类加载器
ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();
System.out.println(classLoader);
// 通过线程获取线程上下文系统类加载器
ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();
System.out.println(classLoader1);
// 通过ClassLoader获取 扩展类加载器
ClassLoader parent = ClassLoader.getSystemClassLoader().getParent();
System.out.println(parent);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
1.5 双亲委派机制
- 如果类加载器收到了加载类的请求,并不会直接加载,而是先把加载类的请求委托给当前类加载器的上层类加载器
- 如果上层加载器还有上层加载器,就会一直往上委派,直到引导类加载器为止
- 再从最上级(引导类加载器)类加载器开始判断是否可以处理类的加载任务,如果可以就加载,如果不能加载,就再次交给下层类加载器加载
以一个自定义类为例,说明类加载过程的双亲委派机制:
自定义类应该由系统加载器加载
- 系统类加载器收到类加载的请求时并不会直接加载类,而是交给上层类加载器,扩展类加载器ExtClassLoader
- 扩展类加载器ExtClassLoader收到系统类加载器的加载请求后,交给上层类加载器引导类加载器(BootStrapClassLoader)
- 引导类加载器收到加载请求后,判断这个类是否由引导类加载器加载,引导类加载器只加载Java核心类库,即以java. javax. sun.开头的包,发现这个自定义类并不属于引导类加载器加载,就返回给扩展类加载器
- 扩展类加载器收到加载请求后,同样的判断这个类是否由扩展类加载器加载(扩展类加载器只加载java.ext.dirs下扩展类),发现这个自定义类也不属于扩展类加载器加载,就会返回系统类加载器
- 系统类加载器再加载这个类.
1.5.1 双亲委派机制的优势
- 防止类的重复加载
- 防止核心api被随意篡改
举例1:
自定义一个类,java.lang.String,自定义的这个类不会被加载,还是会加载java核心类库中的String
package java.lang;
* @Date: 2021/7/16 14:22
public class String{
System.out.println("123");
就会报错:
这是因为,引导类加载器加载java.lang.String,然后执行main方法的时候,由于核心类库中的String并没有main方法,所以直接报错,说明,自定义的这个String类并没有被加载
举例2:
自定义一个类,java.lang.test 这个类在java核心类库中并不存在
* @Date: 2021/7/16 14:52
public class Test {
报错:
安全错误,不允许使用java.lang
也就是不能随便篡改java核心类库,安全
1.5.2 沙箱安全机制
为了保护核心类库不收破坏
1.6 类加载过程的其他
1.6.1 两个Class对象是否属于同一个类
- 类的完整类名必须一样,包括包名
- 必须使用同一个类加载器(类加载器的示例相同)
1.6.2 对类加载器的引用
如果一个类是由非引导类加载器加载(即用户加载器,包括扩展类加载器和系统类加载器),那么jvm会在方法区保存这个类的类加载器的引用信息. 当在解析一个类型到另外一个类型的引用时,jvm需要保证两个类型的类加载器是一样的
1.6.3 类的主动使用和被动使用
主动使用:
- 创建类的实例
- 访问某个类或接口的静态变量,或者对静态变量进行赋值
- 调用类的静态方法
- 反射
- 初始化一个类的子类
- Java虚拟机启动时被标注为启动类的类
- JDK7开始提供的动态语言支持
-
- java.lang.invoke.MethodHandle实例的解析结果
- REF_getStatic,REF_putStatic,REF_invokeStatic句柄对应的类没有初始化,则初始化
除了主动使用的七种情况,其他都是被动使用,被动使用不会初始化类