Java类加载器
类加载器
启动类加载器:Bootstrap ClassLoader
它负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录)下,或被-Xbootclasspath参数指定的路径中的
并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载
启动类加载器是无法被Java程序直接引用的。扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,继承抽象类java.lang.ClassLoader
负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,继承抽象类java.lang.ClassLoader
负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,
如果应用程序中没有自定义过自己的类加载器,默认为 sun.misc.Launcher$AppClassLoader自定义类加载器
类加载过程 装载 -> 验证 -> 准备 -> 解析 -> 初始化
指将.class 中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建这个类的 Class对象,用来封装类在方法取类的对象
- 装载:查找并加载二进制数据
- 链接(分为三个阶段)
- 验证:确保加载类的正确性
- 文件格式验证,确保文件格式符合Class文件格式的规范
- 元数据验证,确保Class的语义描述符合Java的Class规范
- 字节码验证,通过分析数据流和控制流;号引用验证,发生于符号引用转换为直接引用的时候
- 准备:为类的静态变量分配内存,并初始化为默认值
在方法区中为Class分配内存,并设置static成员变量的初始值为默认值,常量的初值即为代码中设置的值,final static tmp = 456
- 解析:将类中的符号引用转换成直接引用
虚拟机将常量池中的符号引用替换为直接引用,解析主要针对的是类、接口、方法、成员变量等符号引用 在转换成直接引用后,会触发校验阶段的符号引用验证,验证转换之后的直接引用是否能找到对应的类、方法、成员变量等
- 验证:确保加载类的正确性
- 初始化:为类的静态变量赋予正确的初始值,在内存中构造一个Class对象来表示该类,即执行类构造器
()的过程
1、对static变量进行赋值的操作,以及static语句块中的操作
2、虚拟机会确保先执行父类的()方法
3、如果一个类中没有static的语句块,也没有对static变量的赋值操作,那么虚拟机不会为这个类生成()方法
4、虚拟机会保证()方法的执行过程是线程安全的(其中一种单例模式的实现就是依赖于此特性)
5、clinit 方法类构造器方法(https://www.jianshu.com/p/8a14ed0ed1e9 )
6、init方法 a: new Object();b:newInstance; clone getObject反序列化
7、接口中不能使用static块,但是接口仍然有变量初始化的操作,因此接口也会生成方法。但接口和类不同的是,不会先去执行继承接口的 方法,而是在调用父类变量的时候,才会去调用 方法 - 类的初始化(不属于加载过程)
1、创建类的实例,也就是new 一个对象
2、访问某个类或接口的静态变量,或者对静态变量赋值
3、调用静态方法
4、反射 Class.forName(“xxxxx”)
5、初始化一个类的子类 会先初始化子类的父类
6、jvm启动时标明启动类,既文件名和类名相同的那个类
类加载过程中使用双亲委派机制
一个类,由不同的类加载器实例加载的话,会在方法区产生两个不同的类,彼此不可见,并且在堆中生成不同Class实例
- 如果用“自定义classLoader1”加载java.lang.String类,那么根据双亲委派最终bootstrap会加载此类,那么bootstrap类就叫做该类的“定义类加载器”,而包括bootstrap的所有得到该类class实例的类加载器都叫做“初始类加载器”
- “命名空间”,是指jvm为每个类加载器维护的一个“表”,这个表记录了所有以此类加载器为“初始类加载器”加载的类的列表
- CLTest是AppClassloader加载的,String是通过加载CLTest的类加载器也就是AppClassloader进行加载,但最终委派到bootstrap加载的
- 所以,对于String类来说,bootstrap是“定义类加载器”,AppClassloader是“初始类加载器”。
String类在AppClassloader的命名空间中(同时也在bootstrap,ExtClassloader的命名空间中,因为bootstrap,ExtClassloader也是String的初始类加载器),所以CLTest可以随便访问String类。这样就可以解释“处在不同命名空间的类,不能直接互相访问”这句话了。
双亲委派加载的意义
- 每一个类都只会被加载一次,避免了重复加载
- 每一个类都会被尽可能的加载(从引导类加载器往下,每个加载器都可能会根据优先次序尝试加载它)
- 效避免了某些恶意类的加载(比如自定义了Java。lang.Object类,一般而言在双亲委派模型下会加载系统的Object类而不是自定义的Object类)
不是同一个类加载器加载的类不具有包级别的访问权限
假如用户调用他编写的java.lang.MyClass类。理论上该类可以访问和改变java.lang包下其他类的默认访问修饰符的属性和方法的能力。
Java语言本身并没有阻止这种行为。但是JVM则会阻止这种行为,因为java核心类库的java.lang包下的类是由bootstrap类加载器加载的
结论
由不同类加载器实例(比如-自定义clsloadr1,-自定义clsloadr2)所加载的classpath下和ext下的类,也就是由我们自定义的类加载器
委派给AppClassloader和ExtClassloader加载的类,在内存中是同一个类吗?
所有继承ClassLoader并且没有重写loadClass方法的类加载器(没有破坏双亲委派机制)得到的AppClassloader都是同一个AppClassloader实例
值得一提的是sun.misc.Launcher类使用了一种类似单例模式的方法,所以我们写的每个类加载器得到的AppClassloader都是同一个AppClassloader
类实例。
这样的话得到一个结论,就是所有通过正常双亲委派模式的类加载器加载的classpath下的和ext下的所有类在方法区都是同一个类,堆中
的Class实例也是同一个。