类的生命周期
一个类从被加载到虚拟机内存中开始,到被卸载出内存为止,整个生命周期包括了 加载、验证、准备、解析、初始化、使用和卸载7个环节。其中,验证、准备、解析 3部分被成为连接。
图中,加载、验证、准备、初始化和卸载5个阶段的开始顺序是确定,但不是按部就班的“进行”或“完成”,这些阶段通常都是相互交叉地混合式进行的通常会在一个阶段执行的过程中调用、激活另一个阶段。
类的加载时机
JVM虚拟机规范并没有对类的加载时机进行严格限制,只规定了一下五种情况需要立刻触发类的初始化:
其余条件下,可以有JVM虚拟机自行决定何时去加载一个类。
- 遇到new、getstatic、putstatic和invokestatic这四个字节码指令时,如果类还没有初始化,则需要先触发其初始化
- 使用反射机制对类进行调用的时候,如果类没有进行过初始化,则需要先触发其初始化
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
- 当虚拟机启动时,用户需要指定一个要执行的主类(包括main()方法的那个类),虚拟机会先初始化这个主类
- 当使用JDK1.7的动态语言支持时,如果一个java.lang.innvoke.Methodhandle实例最后的解析结果REF_getStatic、REF_putStatic、REF _invokeStatic的方法句柄,并且这个方法句柄所对应的类还没有进行过初始化,则需要先出发其初始化
主动引用和被动引用
上面五种条件被称为对类的主动引用,除此之外其他引用类的方式都不会出发初始化,即类的被动引用,举个例子 :
public class Father{
static {
System.out.println("father init.");
}
public static int val=123;
}
public class son extends Father{
static {
System.out.println("son init.");
}
}
当我们访问son.val,会发现输出 “son init.” ,对于静态字段,只会直接定义这个字段的类才会被初始化,因此通过子类来引用父类的静态字段,子类相当于被动引用,也就不会被初始化
类的加载过程
下面简单的介绍一下真个加载过程中,每个阶段JVM都执行了什么操作:
加载(Loading)
加载过程是Java的一大特点,类的来源可以多种多样,压缩包、网络字节流、运行时动态计算生成(reflect)等等······这也就造就Java语言强大的动态特性。
- 通过一个类的完整限定名来获取定义此类的二进制字节流(注意,字节流的来源非常灵活)
- 将这个字节流所代表的静态储存结构转换为方法区的运行时结构数据
- 在内存区域生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证(Verification)
这一过程主要是为了确保Class的字节流中包含的信息符合虚拟机标准,以免造成破坏:
- 文件格式验证
- 元数据验证
- 字节码验证,通过数据流和控制流分析确定程序的语义是合法的
- 符号引用验证,确保解析动作能够正常执行(发生在解析阶段,对类以外的信息进行匹配性校验)
准备(Preparation)
这一阶段将会为类变量分配内存并设置其初始值,此时进行内存分配的只要static变量,冰儿初始值为零值而不是设定值。
除非这个变量被final修饰变成了常量,将会在编译时为其生成ConstantValue属性,准备阶段将会赋予它初始值(非零值)
解析(Resolution)
此阶段虚拟机会将常量池中的符号替换为直接引用。主要包含一下动作:
- 类或结构的解析
- 字段的解析
- 类方法的解析
- 接口方法的解析
初始化(Initialization)
这时类加载过程的最后一步,这部分开始真正的执行Java代码,也就是说,这个阶段,可以有程序员参与。
此阶段其实也就是执行类构造器( )方法的过程。
类加载器
类加载器(ClassLoader)是Java虚拟机的一大创举,它将“获取类的二进制字节流”这个过程交给开发人员自己去实现,只要编写不同的ClassLoader,应用程序本身就可以用相应的方式来获取自己需要的类。
类与加载器的关系
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在虚拟机中的唯一性。
通俗来讲,就是几遍同一个Class文件,被不同的类加载器加载之后,得到的也不是同一个“类”(equals方法返回false)
双亲委派模型
从虚拟机角度将,只有两种类加载器,一种是启动类加载器(Bootstrap ClassLoader),在hotpot上使用C++实现,属于虚拟机的一部分;另一种则是所有其他类的加载器,这些加载器是独立于虚拟机的,由Java语言实现的,从开发者角度来看,可以分为一下两类:
1. 扩展类加载器(Extension ClassLoader)
2. 应用程序类加载器(Application ClassLoader)
当然开发人员也可以自己编写类加载器,最终不同的类加载器之间层次关系如下所示:
这就是Java中著名的双亲委派模型,它要求除了顶级的BootsStrap加载器之外,其他类加载器都必须有父类加载器,工作流程如下:
- 如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是将这个类请求委派给父类加载器来完成,只有当父类加载器反馈自己无法加载这个类的时候,子加载器才会尝试去加载这个类。
- 这样的好处是,Java类随着它的类加载器一起具备了一种具有优先级的层次关系。举个例子,如java.lang.Object这个类,无论哪个类加载器加载时,最终都会委派给Bootstrap加载器来加载,这就保证了真个锡系统运行过程中的Object是同一个类。
- 否则,如果用户自己编写一个java.lang.Object类,并放在程序的classpath中国,最终系统会出现多个不同的Object类,整个Java体系就会变得一团混乱。