类的生命周期
一个类从被加载到内存中开始,他的生命周期包括以下几个阶段:
- 加载
- 验证
- 准备
- 解析
- 初始化
- 使用
- 卸载
上面七个过程中,加载、验证、准备、初始化的顺序是确定的,但是解析和使用由于要支持java语言的运行时绑定,所以顺序不能确定。
类加载时机
类是什么时候开始加载的?虚拟机并没有明确规定,可以由虚拟机自行实现,但是在类初始化前类必须被加载,虚拟机明确规定了有且只有5种情况必须进行类的初始化,因此我们也可以理解为类加载的5个时机。
- 使用new关键字实例化对象、获取或者修改类的静态变量(被final修饰的除外,编译期已经放入常量池)、调用类的静态方法。
- 使用反射技术对类进行调用时。
- 当初始化一个类时,发现其父类仍没有初始化,需要先初始化其父类。
- 当虚拟机启动时,会初始化主类。
- 当使用MethodHandle的方法句柄时。
备注:以上五种场景是类的初始化场景,在初始化时发现类没有被加载就会触发类加载。
类加载要做什么
类的加载需要做以下三件事:
- 通过类的全限定名获取此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个此类的Class对象,作为方法区中类的各种数据访问入口。
类加载器
类的加载是由类加载器加载进内存的,类加载器虚拟机分为两大类,一类是启动类加载器,一类是其他加载器。但是如果细分的话,类加载器可以分为以下三类:
-
启动类加载器(Bootstrap ClassLoader):这个类将器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。
加载Java程序运行所需核心包下的类 -
扩展类加载器(Extension ClassLoader):这个加载器sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
加载java程序运行所需扩展包下的类 -
应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher$App-ClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
加载我们自己编写的类
除了以上三种类加载器外,我们也可以实现自己的类加载器,只需要继承ClaasLoad类并重写loadClass方法即可。
双亲委派模型
上面写的三个类加载器其实是有一定的层级关系的,但又不是简单的继承关系,而是通过组合模式实现的层级关系,除了顶级的启动类加载器外,每个加载器都要有自己的父类加载器,类加载器的这种模型就叫做双亲委派模型。
双亲委派机制
虚拟机自1.2以后,开始提出一直类的加载机制,任何类加载器再接收到加载类请求时,会先请求自己的父类加载器先去加载类,如何父类加载器无法加载再向下传递。所以一个类的加载过程最多要经过一次向上传递请求和一次向下请求才能完成类的加载。
双亲委派机制并不是一个强约束,只是虚拟机团队推荐使用双亲委派机制。
拓展
验证阶段做了什么?
答:验证阶段的主要目的是确保Class文件中的字节流信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全。验证主要包括文件格式、元数据、字节码和符号引用的验证
准备阶段做了什么?
答:准备阶段主要工作是为静态变量分配内存。
解析阶段做了什么?
答:解析阶段主要是对类、接口、字段、类的静态方法和接口方法进行解析。
扫码关注公众号 精彩不缺过