类加载器
类的加载、连接、初始化--类加载或类初始化
类的加载
将类的class文件读入内存,并创建一个java.lang.Class对象--类加载器的功能
类加载器负责加载类,使用不同的类加载器,从不同的来源加载类的二进制数据。来源:
- 从本地文件系统加载class文件
- 从jar包加载class文件
- 通过网络加载class文件
- 把Java源文件动态编译,并执行加载
类加载器
Java中,一个类用全限定类名(包名和类名)作为标识;在JVM中,一个类用全限定类名和其类加载器作为其唯一标识。
JVM启动时,3个类加载器组成初始加载结构:
- Bootstrap ClassLoader :根类加载器
- Extention ClassLoader :扩展类加载器
- System ClassLoader :系统类加载器
根类加载器:并不是java.lang.ClassLoader 的子类,JVM自身实现
扩展类加载器 :负责加载JRE的扩展目录中JAR包的类 (URLClassLoader的子类)
系统类加载器:负责在JVM 启动时加载来自Java命令的-classpath选项、java.class.path系统属性或CLASSPATH环境变量所指 定的JAR包和类路径 (URLClassLoader的子类)
类的加载机制
- 全盘负责 :加载一个class,它所依赖或引用的其他class也由同一加载器加载,除非显式载入
- 父类委托 :先让父类加载器加载,父类无法加载才尝试从自己的类路径中加载
- 缓存机制 :所有加载过的class会被缓存,当需要某个class时,先从缓存中寻找,不存在时系统再读取类的二进制数据, 并 转换成Class对象,存入缓存区。(修改Class后必须重启JVM,程序的修改才能生效的原因)
类加载器之间的父子关系并不是继承上的父子关系,二是类加载器实例之间的关系
类加载器的步骤
首先检测此Class是否加载过,是则返回对应的jav.lang.Class对象;否,则查看父类加载器是否存在,是则用父类加载器加载,否--一是向上查看父类加载器是否存在,存在则尝试寻找Class文件,找到就载入Class;二是使用根类加载器加载,如加载不成功,则抛出ClassNotFoundException异常。
类的连接
把类的二进制数据合并到JRE中
分为3个阶段:
- 验证:被加载的类是否有正确的内部结构,是否和其他类一致
- 准备:为类的静态Field分配内存,设置默认初始值
- 解析:将类的二进制数据中的符号引用替换成直接引用
类的初始化
由虚拟机负责,主要对静态Field进行初始化,指定初始值有两种方式:
- 声明静态Field指定初始值
- 使用静态初始化块为静态Field指定初始值
public class test{
static{
b=6; //使用静态初始化块指定初始值
}
static int a=8; //声明时指定初始值
}
初始化一个类的步骤:
- 类还没有被加载和连接,先加载并连接
- 类的直接父类还没初始化,先初始化其直接父类
- 类中有初始化语句,一次执行初始化语句
直接父类的初始化同样遵循这个步骤,由此,JVM总是最先初始化java.lang.Object类。此类及其所有父类都会被初始化
初始化的时机
6种方式初始化某类或接口:
- 创建类的实例 创建实例的方式(使用new操作符;通过反射;通过反序列化;使用clone())
- 调用某个类的静态方法
- 访问某个类或接口的静态Field,或为其赋值
- 使用反射强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
当某个静态Field使用了final修饰,而且它的值在编译时就确定下来,那么程序在其他地方使用时将其当作常量使用;
反之,值不能在编译时确定下来,运行时才能确定该Field的值,通过该类来访问它的静态Field,会导致该类被初始化。
注解