知识学习——Java类的生命周期

来源:互联网 发布:单片机65536 编辑:程序博客网 时间:2024/06/06 05:12

类的生命周期

1、类的加载、连接和初始化

这里写图片描述

(1)加载:查找并加载类的二进制数据。
(2)连接

  1. 验证:确保加载类的正确性
  2. 准备:为类的静态变量分配内存,并将其初始化为默认值。
  3. 解析:把类中的符号引用转换为直接引用

(3)初始化:给类的静态变量赋予正确的初始值。

类的加载

类的加载是先把类的.class文件加载入内存,放到运行时数据区的方法区内,然后在堆区创建一个java.lang.classs对象,用来封装类在方法区的数据结构。

Java读取多种来源的二进制文件,包括:
(1) 本地系统加载类的.CLASS文件;
(2) 网络下载类的.class文件;
(3) 通过zip或.jar包的文件中提取的.class文件;
(4) 专有数据库中提取.class文件;
(5) 把一个java源文件动态编译为.class文件。

类的加载最终产品是位于运行时数据区的的堆区的CLASS文件,其封装了方法区内的数据结构,并提供了java程序提供了访问类在方法区内的数据接口。

类的加载是由JAVA类加载器完成的:分为两种:

(1)java虚拟机自带的类加载器,包括启动类的加载器、扩展类的加载器和系统类加载器;
(2)用户自定义的类加载器,可以由java.lang.ClassLoader类的子类的实现,用户用它可以定制类加载方式。

类加载器并不是在首次使用类的时候才加载它,java虚拟机规范允许类加载器在预料到某个类将要调用的时候预先加载它,如果预先加载中文件缺失,类加载器在首次调用该类才报告错误(LinkageError错误)。若一直未被程序主动调用,则类加载器不会报错。

类的验证

当类被加载后,就进入连接阶段,连接就是把已经读入内存的类的二进制数据合并到虚拟机的运行时环境中去。

第一步是类的验证,保证加载的类内部结构与其他类的协调一致。若检查到错误,就会抛出Error对象。

类验证主要包括:

  • 类文件结构检查;
  • 语义检查,确保符合JAVA规范,比如final类没有子类,以及final类型的方法没有被覆盖;
  • 字节码检查,字节码代表JAVA方法(包括静态方法和实例方法),他是由被称作操作码的单字节指令组成的序列;
  • 二进制兼容性验证,确保相互引用指教协调一致。如Worker类的goWork()方法会调用Car类的run()方法,若不存在NoSuchMethodError错误

类的准备

准备阶段,Java虚拟机为类的静态变量分配内存,并设置默认的初始值

类的解析

解析阶段,就是把二进制数据中的符号引用替换为直接引用

类的初始化

给类的静态变量赋予初始值。

Java虚拟机初始化一个类包括以下步骤:

(1) 若未加载和连接则先加载和连接;
(2) 若有父类未初始化,则初始化之;
(3) 若有初始化化语句则执行之;一般为静态变量或者构造方法;

类的初始化的时机

Java对类的使用方式分为主动使用和被动使用

Java虚拟机只在程序首次访问一个类或接口时候才初始化它。只有6种情况被看做程序对类或接口的主动使用

(1) 创建类的实例。包括用new语句创建实例,或通过反射、克隆及反序列化来创建;
(2) 调用类的静态方法;
(3) 访问某个类或接口的静态变量,或者对静态变量赋值;
(4) 反射方法调用,如调用Class.forName(“Worker”)方法,若该类未初始化,那么改程序就会初始化之,饭后Worker类的实例。
(5) 初始化子类的时候,会主动初始化父类;
(6) 被JAVA虚拟机标明的启动类的类

除以上6种情况外,其他情况都会被认为被动使用,都不会导致类的的初始化,如:

(1) final类的静态变量,如果在编译就能计算出变量的取值,那么该变量就叫做编译时常量,就会被看做对该类的被动调用,不会导致该类初始化,
(2)若对于final类型的静态变量,编译时候不能计算出变量的取值,那么该类就会看做对类的主动调用,会导致初始化

   public static final inta=(int)(Math.random()*10)/10+1;

结果该类被初始化。

(3)只有当程序访问的静态变量和静态方法的确在当前类或接口中定义时,才可以被看作对类和接口的主动使用。
(4) 调用ClassLoader类的loadClass方法加载一个类,不是对类的主动使用,不会初始化,当调用forName(“classA”)方法时才会初始化,

2、类加载器

类加载器就是指把类加载到JAVA虚拟机中去,从JDK1.2开始,类的加载过程采用父亲委托机制,这种机制能更好的保证JAVA平台安全。

在委托机制中,除了虚拟机自带的根类加载器以外,其余的类加载器都有一个父加载器,当程序请求加载器loader1加载Sample类时候,loader1首先委托自己的父加载器去加载Sample类,若加载成功,则由类加载器完成,否则由loader1本身加载该类。

Java虚拟机自带以下几种加载器

(1) 根(Bootstrap)类加载器:该加载器没有父加载器,它负责加载虚拟机的核心类库,如java.lang.*等。跟类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。根类加载器的实现依赖于底层操作系统,属于虚拟机实现的一部分,它并没有继承java.lang.ClassLoader类。
(2)扩展(Extension)类加载器:它的父加载器为根类加载器,从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK安装目下的jre/lib/ext子目录下加载类库,若吧JAR文件放到该目录下,会自动由扩展类加载,扩展类加载器是纯JAVA类,是java.lang.ClassLoader类的子类。
(3)系统(System)类加载器:也称应用类加载器,它的父加载器为扩展类加载器。它从classpath环境变量或者系统属性java.class.path所指定目录中加载类,它是用户自定义的类加载器的默认父加载器,系统类加载器是纯java类,是java.lang.ClassLoader类的子类。
(4)用户可以定制自己的类加载器,java提供了抽象类java.lang.ClassLoader,用户自定义类继承自该类就可以了。

这里写图片描述

类加载的父亲委托机制

在父亲委托机制中,各个机制按照父子关系形成了树形结构,除了根类加载器意外,其余的类加载器有且只有一个父加载器。

这里写图片描述

Class smapleClass=loader2.loadClass(“Sample”);

loader2首先从自己的命名空间中查找Sample类是否已经被加载,如果已经加载就直接返回代表Sample类的Class对象的引用。

如果Sample类还没有被加载,loader2首先请求loader1代为加载,loader1在请求系统类加载器代为加载,系统类加载器在请求扩展类加载器代为加载,扩展类加载器在请求根类加载器代为加载,若根类加载器和扩展类加载器都不能加载,则系统类加载器尝试加载,若能加载成功,则将Sample类所对顶的Class对象的引用返回给loader1,loader1在将引用返回给loader2,从而成功将Sample类加载进虚拟机。若系统类加载器不能加载Sample类,则loader1尝试加载Sample类,若loader1也不能成功加载,则loader2尝试加载,所所有的父加载器以及loader2本身都不能加载,则抛出ClassNotFoundException异常。

若有一个类加载器能成功加载Sample类,那么这个类加载器被称为定义类加载器,所有能成功返回Class对象的引用的类加载器(包括定义类加载器)以及所有子类加载器都被成为初始类加载器。

例如:loader1实际加载了Sample类,则loader1为Sample类的定义类加载器,loader2和loader1为Sample类的初始类加载器。

加载器之间的父子关系实际上指的是加载器对象之间的包装关系,而不是类之间的继承关系。一对父子加载器可能是同一个加载器类的两个实例,也可能不是。在子类加载器对象中包装了一个父类加载器对象。例如一下loader1和loader2都是MyClassLoader类的实例,并且loader2包装了loader1,loader1是loader2的父类加载器。

当生成一个自定义的类加载器实例时。如果没有指定他的父加载器,那么系统类加载器将成为该类加载器的父加载器。
API中解释:
Creates a new class loader using the ClassLoader returned by the method getSystemClassLoader() as the parent class loader.
父亲委托机制的优点是能够提高软件系统的安全性。因此在此机制下,用户自定义的类加载器应该有父类加载器加载的可靠类,从而防止不可靠甚至恶意的代码代替由父加载器加载的可靠代码。例如:java.lang.Object类总是由根类加载器加载,其他任何用户自定义的类加载器都不可能加载含有恶意代码的java.lang.Object类。

命名空间

每个类加载器都有自己的命名空间,命令空间由该加载器及其所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。
运行时包

由同一类加载器加载的属于相同包的类组成了运行时包。决定两个类是不是属于同一个运行时包,不仅要看他们的包名是否相同,还要看定义类加载器是否相同。只有属于同意运行时包的类才能互相访问包可见(即默认访问级别)的类和类成员。这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员。建设用户自己定义了一个类java.lang.SPy,并用用户自定义的类加载器加载,由于java.lang.Spy和核心类库java.lang.*由不同的加载器加载,他们属于不同的运行时包,所以java.lang.Spy不能访问核心类库java.lang包中的包可见成员。

3、类的卸载

在类使用完之后,如果满足下面的情况,类就会被卸载:

  • 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
  • 加载该类的ClassLoader已经被回收。
  • 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

如果以上三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,java类的整个生命周期就结束了。

原创粉丝点击