java-类的生命周期

 一个java文件从被加载到被卸载这个生命过程,总共要经历5个阶段,JVM将类加载过程分为:
  加载->链接(验证+准备+解析)->初始化(使用前的准备)->使用->卸载
  
装载
装载 二进制形式的java类型读入java虚拟机中

通过该类型的完全限定名 产生一个代表该类型的二进制数据流
解析二进制数据流为方法区内的内部数据结构
创建一个表示该类型的java.lang.Class类的实例

jvm必须在每个类或接口首次主动使用时初始化,包括六种情形

1、创建某个类的新实例时,通过在字节码中执行new 通过不明确的创建 反射 克隆或者反序列化
2、调用某个类的静态方法 字节码中执行invokestatic指令
3、使用某个类或接口的静态字段 或者对该字段赋值时 字节码中 执行getstatic或者putstatic指令
用final修饰的静态字段除外 它被初始化一个编译时的常量表达式
4、调用JAVA API中的某些反射方法时,如类class中的方法或者java.lang.reflect包中的类方法
5、初始化某个类的子类 某个类初始化时 要求超类初始化
6、虚拟机启动时某个被标明为启动类的类 即含有main方法的类

某个类的所有祖先类必须在该类之前被初始化
但是对于接口来说 不适用

只有在某个接口所声明的非常量字段被使用时 该接口才会被初始化

任何一个类的初始化要求它所有祖先类预先被初始化
一个接口的初始化 不要求祖先接口初始化

产生 类型的二进制数据 方式

从本地文件系统装载一个java class 文件
通过网络下载一个java class
从一个ZIP JAR CAB或者其他归档文件中提取java class
从一个专有数据库中提取java class
把一个Java源文件动态编译为class文件格式
动态为某个类型计算其class文件数据
其他

Class类的实例对象
成为java程序与内部数据结构之间的接口

存储在内部数据结构中的
程序要调用该类型对应的Class实例对象的方法

创建类型
类型的二进制数据解析为方法区内部数据结构,并在堆上建立一个class对象

启动类ClassLoader
虚拟机一部分 与实现无关方式装载类型

用户自定义ClassLoader
java.lang.ClassLoader的子类 定制

ClassLoader缓存java类型的二进制表现形式
预料某个类型将要使用时装载
把这些类型装载到一些相关的分组

预先装载遇到缺失或错误的class文件 必须等到程序首次主动使用才报告错误

加载时 解析二进制数据流 构造内部数据结构
特定检查 保证解析二进制数据的初始工作不会导致虚拟机崩溃
检查二进制数据完整的格式

Java class 文件格式解析器 检查

确保除了Object之外的每一个类都有超类
当虚拟机装载一个类时 必须确保该类的所有超类已经被装载

验证符号引用

动态连接的过程包括通过保存在常量池中的符号引用查找被引用的类
接口 字段以及方法
把符号引用替换成直接引用
确保元素存在,有访问该元素的权限

连接
已经读入虚拟机的二进制形式的类型数据合并到虚拟机的运行时状态中去

验证

确认类型符合java语义 不危及jvm的完整性

特定类型的检查一般在特定的时间

确保各个类之间二进制兼容的检查:

检查final的类不能拥有子类
检查final方法不能覆盖
确保在类型和超类型之间没有不兼容的方法声明
两个方法拥有同样的名字 参数在数量 顺序 类型上相同 但是返回类型不同

当实现了父接口的子类或者扩展了父接口的子接口 被装载时
父接口也必须被装载

验证期间 类和它所有的超类型都需要确保互相之间仍然二进制兼容

检查所有的常量池入口相互之间一致
比如 一个CONSTANT_String_info 入口的string_index 项目必须是一个
CONSTANT_Utf8_info入口的索引

检查常量池中的所有的特殊字符串
类名 字段名和方法名 字段描述符和方法描述符 是否符合格式

检查字节码的完整性

最复杂的任务就是字节码验证
所有的java虚拟机必须设法为它们执行的每个方法检验字节码的完整性

比如 不能因为一个超出了方法末尾的跳转指令 就导致虚拟机实现崩溃
必须在字节码验证的时候检查出这样的跳转指令是非法的 从而抛出一个错误

使得字节码流可以通过一次性使用一个数据流分析器进行验证
在连接过程中一次性验证字节码流

通过一个数据流分析器进行字节码验证的时候
为了确保符合java语言的语义而装载其他的类

数据流分析器 字节码验证的时候
虚拟机可能不得不为了确保符合java语言的语义而装载其他的类
比如 设想一个类包括一个方法 其中把一个java.lang 的实例的引用赋值给了
一个java.lang.Number类型的字段 只需要装载它

准备
为类变量分配内存 设置默认初始值
把给类变量新分配的内存根据类型设置为默认值

int 0
long 0L
short 0
char \u0000
byte 0 
boolean false 
reference null
float 0.0f 
double 0.0d  

n内部 boolean 尝尝被实现为int 默认设置为0 false

可能为数据结构分配内存
如方法表
包含指向类中每个方法 从超类继承方法的指针

方法表可以使得继承的方法 执行时 不需要搜索超类

解析

在类型的常量池中寻找类 接口 字段和方法的符号引用
转换成直接引用

在符号引用被程序首次使用之前 连接的这个步骤是可选的

初始化

为类变量赋予正确的初始值 程序员希望的

正确的初始值 是和在准备阶段赋予的默认初始值对比而言

根据类型不同
类变量已经被赋予了默认初始值

正确的初始值根据程序员制定的主观计划而生成

正确初始值通过类变量初始化语句或者静态初始化语句给出

static int size = 3 * (int)(Math.random()*5.0)

class Examplelb{

    static int size; 

    static {
        size = 3 * (int)(Math.random()*5.0);
    }

}

所有的类变量初始化语句和类型的静态初始化器 都被
java编译器收集在一起
放到一个类初始化方法

对接口来说 被称为接口初始化方法

在类和接口的java class文件中 称为

只能被java 虚拟机调用 专门把类型静态变量设置为正确初始值

初始化类包括两个步骤:
如果类存在直接超类 直接超类没有被初始化 先初始化直接超类
第一个被初始化的类 永远是Object
然后是被主动使用的类的继承树上所有的类

超类总是在子类之前初始化

如果接口存在初始化方法的话 就执行此方法

存在一个类初始化方法 执行此方法

jvm 调用类的()方法之前
必须确认超类的()方法被执行

jvm 必须确保初始化过程被正确同步
如果多个线程需要初始化一个类
仅仅允许一个线程来执行初始化
其他的线程需要等待

当活动的线程完成 初始化过程后
必须通知其他等待的线程


()方法
java 编译器把类变量初始化语句和静态初始化语句的
代码都放到class文件的()方法中
顺序就按照他们在类或接口声明中出现的顺序

class Examplelc {

    static int width;
    static int height=(int)(Math.random() * 2.0);

    static{
        width=3*(int)(Math.random()*5.0)
    }

}

() 首先执行Example 唯一的一个类变量初始化代码
初始化了height变量

然后执行静态初始化语句
初始化了width变量

类变量初始化语句出现在静态初始化语句之前

如果类没有声明任何类变量 没有静态初始化语句
就不会有()方法

如果类声明了类变量
没有明确使用类变量初始化语句或者静态初始化语句初始化他们
或者仅包含静态final变量的类变量初始化语句
就不会有 clinit

只有的确需要执行java代码来赋予类变量正确初始值的类才会有

如果接口包含任何不能在编译时解析成一个常量的
字段初始化语句 接口 就会有一个cinit方法

interface Example{
int ketchup = 5;
int mustard = (int)(Maht.random()*5.0):
}

六种主动使用类型的活动
1、创建类的新实例
2、调用类中声明的静态方法
3、操作类或者接口中声明的非常量静态字段
4、调用java api中特定的反射方法
5、初始化一个类的子类
6、指定一个类作为java虚拟机启动时的初始化类

使用一个非常量的静态字段只有当类或者接口的确声明了这个字段时才是主动使用
比如,类中声明的字段可能会被子类引用
接口中声明的字段可能会被子接口或者实现了这个接口的类引用,这是被动使用
不会触发初始化

class NewParent{
   static int hoursOfSleep = (int)(Math.random()*3.0);
   static {
    System.out.println("。。。");  
  }
}

class NewBornBaby extends NewParent {
    static int hoursOfCrying = 6 +(int)(Math.random()*2.0):
    static {
       system.out.println("NewbornBay was initialized.");       
    }
}

class Example2{
    public static void main(String[] args){
        int hours = NewbornBaby.houseOfSleep;
        System.out.println(hours);
    }

    static{
        System.out.println("Example2 was initialized");
    }
}

静态final字段特殊处理

interface Anary{
    String greeting="GRRR!";
    int angerLevel = Dog.getAngerLevel();
}

class Dog{
    static final String greeting = "Woof,woof,world!";

    static {
        System.out.println("Dog was initialized");
    }

    static int getAngerLevel(){
        System.out.println("Angry was initalized"):
        return 1;
    }
}

class Example3 {
    public static void main(String[] args){
        System.out.println(Angry.greeting):
        System.out.println(Dog.greeting);
    }

    static {
        System.out.println("Example3 was initalized");
    }
}

猜你喜欢

转载自blog.csdn.net/qq_16038125/article/details/80736374