当程序使用某个类时,如果该类还没被初始化,加载到内存中,则系统会通过加载、连接、初始化三个过程来对该类进行初始化。该过程就被称为类的初始化
类加载
指将类的class文件读入内存,并为之创建一个java.lang.Class的对象
类文件来源
- 从本地文件系统加载的class文件
- 从JAR包加载class文件
- 从网络加载class文件
- 把一个Java源文件动态编译,并执行加载
类加载器通常无须等到“首次使用”该类时才加载该类,JVM允许系统预先加载某些类
类加载器
类加载器就是负责加载所有的类,将其载入内存中,生成一个java.lang.Class实例。一旦一个类被加载到JVM中之后,就不会再次载入了。
- 根类加载器(Bootstrap ClassLoader):其负责加载Java的核心类,比如String、System这些类
- 拓展类加载器(Extension ClassLoader):其负责加载JRE的拓展类库
- 系统类加载器(System ClassLoader):其负责加载CLASSPATH环境变量所指定的JAR包和类路径
- 用户类加载器:用户自定义的加载器,以类加载器为父类
类加载器之间的父子关系并不是继承关系,是类加载器实例之间的关系
1 | public static void main(String[] args) throws IOException { |
结果
1 | 系统类加载 |
为什么根类加载器为NULL?
根类加载器并不是Java实现的,而且由于程序通常须访问根加载器,因此访问扩展类加载器的父类加载器时返回NULL
JVM类加载机制
- 全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
- 父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
- 缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效
URLClassLoader类
URLClassLoader为ClassLoader的一个实现类,该类也是系统类加载器和拓展类加载器的父类(继承关系)。它既可以从本地文件系统获取二进制文件来加载类,也可以远程主机获取二进制文件来加载类。
两个构造器
URLClassLoader(URL[] urls):使用默认的父类加载器创建一个ClassLoader对象,该对象将从urls所指定的路径来查询并加载类
URLClassLoader(URL[] urls,ClassLoader parent):使用指定的父类加载器创建一个ClassLoader对象,其他功能与前一个构造器相同
1 | import java.net.MalformedURLException; |
获得URLClassLoader对象后,调用loanClass()方法来加载指定的类
自定义类加载器
1 | import java.io.File; |
JVM中除了根类加载器之外的所有类的加载器都是ClassLoader子类的实例,通过重写ClassLoader中的方法,实现自定义的类加载器
loadClass(String name,boolean resolve):为ClassLoader的入口点,根据指定名称来加载类,系统就是调用ClassLoader的该方法来获取制定累对应的Class对象
findClass(String name):根据指定名称来查找类
推荐使用findClass方法
类的链接
当类被加载后,系统会为之生成一个Class对象,接着将会进入连接阶段,链接阶段负责把类的二进制数据合并到JRE中
三个阶段
验证:检验被加载的类是否有正确的内部结构,并和其他类协调一致
准备:负责为类的类变量分配内存。并设置默认初始值
解析:将类的二进制数据中的符号引用替换成直接引用
类的初始化
JVM负责对类进行初始化,主要对类变量进行初始化
在Java中对类变量进行初始值设定有两种方式:①声明类变量是指定初始值②使用静态代码块为类变量指定初始值
JVM初始化步骤
- 假如这个类还没有被加载和连接,则程序先加载并连接该类
- 假如该类的直接父类还没有被初始化,则先初始化其直接父类
- 假如类中有初始化语句,则系统依次执行这些初始化语句
类初始化时机
- 创建类实例。也就是new的方式
- 调用某个类的类方法
- 访问某个类或接口的类变量,或为该类变量赋值
- 使用反射方式强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类,则其父类也会被初始化
- 直接使用java.exe命令来运行某个主类