类的生命周期
类加载的时机
加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,但解析阶段则不一定,它在某些情况下可以在初始化阶段之后再开始。
虚拟机规范严格规定了有且只有五种情况必须立即对类进行初始化”:
+ 使用new关键字实例化对象的时候、读取或设置一个类的静态字段的时候,已经调用一个类的静态方法的时候。
+ 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化,则需要先触发其初始化。
+ 当初始化一个类的时候,如果发现其父类没有被初始化就会先初始化它的父类。
+ 当虚拟机启动的时候,用户需要指定一个要执行的主类(就是包含main()方法的那个类),虚拟机会先初始化这个类;
+ 使用Jdk1.7动态语言支持的时候的一些情况
除此之外所有引用类的方式都不会触发初始化称为被动引用,下面是3个被动引用例子:
- 通过子类引用父类静态字段,不会导致子类初始化
- 通过数组定义引用类,不会触发此类的初始化
- 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用定义常量的类,因此不会触发定义常量的类的初始化
加载
加载过程:
通过类型的完全限定名,产生一个代表该类型的二进制数据流(没有指明从哪里获取、怎样获取,是一个非常开放的平台),加载源包括:文件(Class文件,Jar文件)、网络、计算生成(代理$Proxy)、由其它文件生成(jsp)、数据库中;
解析这个二进制数据流为方法区内的运行时数据结构;
创建一个表示该类型的java.lang.Class类的实例,作为方法区这个类的各种数据的访问入口
连接-校验
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。从整体上看,验证阶段大致上会完成4个阶段的校验工作:文件格式、元数据、字节码、符号引用。
- 文件格式验证:主要验证字节流是否符合Class文件格式规范,并且能被当前的虚拟机加载处理。例如:主,次版本号是否在当前虚拟机处理的范围之内。常量池中是否有不被支持的常量类型。指向常量的中的索引值是否存在不存在的常量或不符合类型的常量。
- 元数据验证:对字节码描述的信息进行语义的分析,分析是否符合java的语言语法的规范。
- 字节码验证:最重要的验证环节,分析数据流和控制,确定语义是合法的,符合逻辑的。主要的针对元数据验证后对方法体的验证。保证类方法在运行时不会有危害出现。
- 符号引用验证:主要是针对符号引用转换为直接引用的时候,是会延伸到第三解析阶段,主要去确定访问类型等涉及到引用的情况,主要是要保证引用一定会被访问到,不会出现类等无法访问的问题。
连接-准备
准备阶段正式为类变量分配内存并设置变量的初始值。这些变量使用的内存都将在方法区中进行分配。注:这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
初始值通常是数据类型的零值;对于:public static int value = 123,那么变量value在准备阶段过后的初始值为0而不是123,这时候尚未开始执行任何java方法,把value赋值为123的动作将在初始化阶段才会被执行。对于:public static final int value = 123;编译时Javac将会为value生成ConstantValue(常量)属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123
连接-解析
解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
- 符号引用(Symbolic References): 符号引用以一组符号来描述所引用的目标,符号可以是符合约定的任何形式的字面量,符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。
-
直接引用(Direct References): 直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用与虚拟机实现的内存布局相关,引用的目标必定已经在内存中存在。
初始化
初始化阶段是执行类构造器
父类中定义的静态语句块要优于子类的变量赋值操作。
如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生产
虚拟机会保证一个类的
类加载器
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性。如果两个类来源于同一个Class文件,只要加载它们的类加载器不同,那么这两个类就必定不相等。
启动类加载器(Bootstrap ClassLoader)
这个类加载器负责将存放在\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。getClassLoader()方法返回null。
扩展类加载器(Extension ClassLoader)
这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
应用程序类加载器(Application ClassLoader)
这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。