官方网站下载抖音,杭州专业做网站公司,阿里云 ip 网站,网站 编程语言【Java基础】Java对象的生命周期
一、概述
一个类通过编译器将一个Java文件编译为Class字节码文件#xff0c;然后通过JVM中的解释器编译成不同操作系统的机器码。虽然操作系统不同#xff0c;但是基于解释器的虚拟机是相同的。java类的生命周期就是指一个class文件加载到类…【Java基础】Java对象的生命周期
一、概述
一个类通过编译器将一个Java文件编译为Class字节码文件然后通过JVM中的解释器编译成不同操作系统的机器码。虽然操作系统不同但是基于解释器的虚拟机是相同的。java类的生命周期就是指一个class文件加载到类文件注销整个过程。
一个java类的完整的生命周期会经历加载-连接-初始化-使用-卸载五个阶段当然也有在加载或连接之后没有被初始化就直接被使用的情况类加载的过程如下图 二、加载loading
java类生命周期加载loading是有类加载器完成(类加载器分为BootstrapClassLoader,ExtClassLoader,AppClassLoader)类的class二进制文件读取到内存后并将其保存到方法区内然后就创建一个java.lang.Class类型的对象。类被加载入JVM中同一个类只被载入一次。加载是类加载的第一个环节。
java生命周期加载loading阶段主要做三件事
类加载器ClassLoader通过一个类的全称获取二进制Class文件加载到JVM内存中如果已经获取过则直接返回其Class对象。将字节流所代表的静态存储结构转化为JVM方法区的运行时数据结构。在内存中生产一个代表此类的java.lang.Class的对象作为访问这个类的入口。
二、连接Linking
当类被加载之后系统为了生成一个对应的java.lang.Class对象接着将会进入连接阶段连接阶段会负载把类的二进制数据合并到JVM的运行状态中。类的连接可以分为如下三个阶段 验证确保java类型的数据格式正确并使用与jvm使用验证作为连接阶段的第一个阶段这个阶段确保Class文件的字节流中包含的信息符合当前虚拟机的要求并且不会危害虚拟机自身的安全。 验证包含一下内容 文件格式验证如是否以魔数 0xCAFEBABE 开头、主、次版本号是否在当前虚拟机处理范围之内、常量合理性验证等。此阶段保证输入的字节流能正确地解析并存储于方法区之内格式上符合描述一个 Java类型信息的要求元数据验证是否存在父类父类的继承链是否正确抽象类是否实现了其父类或接口之中要求实现的所有方法字段、方法是否与父类产生矛盾等。第二阶段保证不存在不符合 Java 语言规范的元数据信息字节码验证通过数据流和控制流分析确定程序语义是合法的、符合逻辑的。例如保证跳转指令不会跳转到方法体以外的字节码指令上符号引用验证在解析阶段中发生保证可以将符号引用转化为直接引用。 可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施以缩短虚拟机类加载的时间。 准备 为该类分配内存就是为类的静态变量分配内存并设为jvm默认的初值对于非静态的变量则不会为它们分配内存。静态变量的初值是由JVM自动分配初始值。 JVM默认的初值如下 内存分配的仅包括类变量static而不包括实例变量实例变量会在对象实例化时随着对象一块分配在Java堆中设置的初始值通常情况下是数据类型默认的零值如0、0L、null、false等而不是被在Java代码中被显式地赋予的值对基本数据类型来说对于类变量static和全局变量如果不显式地对其赋值而直接使用则系统会为其赋予默认的零值而对于局部变量来说在使用前必须显式地为其赋值否则编译时不通过。对于同时被static和final修饰的常量必须在声明的时候就为其显式地赋值否则编译时不通过而只被final修饰的常量则既可以在声明时显式地为其赋值也可以在类初始化时显式地为其赋值总之在使用前必须为其显式地赋值系统不会为其赋予默认零值。如果在数组初始化时没有对数组中的各元素赋值那么其中的元素将根据对应的数据类型而被赋予默认的零值。 解析:解析阶段就是虚拟机将常量池中的符号引用转化为直接引用的过程。 符号引用 符号引用是一个字符串它给出了被引用的内容的名字并且可能会包含一些其他关于这个被引用项的信息——这些信息必须足以唯一的识别一个类、字段、方法。这样对于其他类的符号引用必须给出类的全名。对于其他类的字段必须给出类名、字段名以及字段描述符。对于其他类的方法的引用必须给出类名、方法名以及方法的描述符。 直接引用可以是以下三种情况 直接指向目标的指针比如指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针相对偏移量比如指向实例变量、实例方法的直接引用都是偏移量个能间接定位到目标的句柄 直接引用是和虚拟机的布局相关的同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用那引用的目标必定已经被加载入内存中了 解析阶段可能开始于初始化之前也可能在初始化之后开始虚拟机会根据需要来判断到底是在类被加载器加载时就对常量池中的符号引用进行解析初始化之前还是等到一个符号引用将要被使用前才去解析它初始化之后 解析主要分为两种情况 类或接口的解析判断所要转化成的直接引用是对数组类型还是普通的对象类型的引用从而进行不同的解析。字段解析对字段进行解析时会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段如果有则查找结束如果没有则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口还没有则按照继承关系递归搜索其父类直至查找结束。 解析阶段的静态绑定和动态绑定 静态绑定static binding也叫前期绑定在程序执行前该方法就能够确定所在的类此时由编译器或其它连接程序实现比如构造方法或者被static或final修饰的。动态绑定auto binding也叫后期绑定在运行时虚拟机根据具体对象的类型进行绑定或者说是只有对象在虚拟机中创建了之后才能确定方法属于哪一个对象比如含有泛型的。
三、初始化(Initialization)
执行静态变量的初始化和静态Java代码块并初始化已设置好的变量值。需要注意的是加载、验证和装备阶段只会进行一次而初始化是可以重复进行的。在准备阶段类变量已经被初始化过一次系统提供的默认值而在初始化阶段则是根据java代码中实际指定的值去初始化类变量和其它内容。
类的初始化即是执行类构造器clinit()方法的过程规则如下
clinit方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的编译器收集的顺序是由语句在源文件中出现的顺序所决定的静态语句块中只能访问到定义在静态语句块之前的变量定义在它之后的变量则只可以赋值而不能访问。方法与实例构造器方法类的构造函数不同它不需要显式地调用父类构造器虚拟机会保证在子类的方法执行之前父类的方法已经执行完毕。因此在虚拟机中第一个被执行的方法的类肯定是java.lang.Objectclinit方法对于类或接口来说并不是必须的如果一个类中没有静态语句块也没有对类变量的赋值操作那么编译器可以不为这个类生成clinit方法。接口中不能使用静态语句块但仍然有类变量final static初始化的赋值操作因此接口与类一样会生成方法。但是接口鱼类不同的是执行接口的方法不需要先执行父接口的方法只有当父接口中定义的变量被使用时父接口才会被初始化。另外接口的实现类在初始化时也一样不会执行接口的方法。虚拟机会保证一个类的方法在多线程环境中被正确地加锁和同步如果多个线程同时去初始化一个类那么只会有一个线程去执行这个类的方法其他线程都需要阻塞等待直到活动线程执行方法完毕。如果在一个类的方法中有耗时很长的操作那就可能造成多个线程阻塞在实际应用中这种阻塞往往是很隐蔽的。
触发对象初始化的场景:
通过new关键字实例化对象、读取或设置类的静态变量、调用类的静态方法。通过反射方式实例化对象如Class.forName()方法。初始化子类的时候会触发父类的初始化。虚拟机启动时初始化一个执行主类也就是直接调用main方法。使用(反)序列化机制创建对象。使用JDK7的动态语言支持时如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄并且这个方法句柄对应的类没有进行初始化则需要先触发其初始化。
初始化的原则 按照顺序自上而下运行类中的变量赋值语句和静态语句如果有父类则首先按照顺序依次递归运行父类中的变量赋值语句和静态语句。
四、使用Using)
当一个对象初始化完成后就生成了一个对象的实例。
访问类变量和方法不需要实例化静态代码块只会被调用一次而实例的代码块则是每次初始化调用一次通过final修饰符可以防止类被继承或者变量的值被修改设置访问权限限制其它对象的访问
实例化一个类大概有四种途径
New操作符调用Class或者Java.lang.reflect.Constructor对象的newInstance()方法调用任何现有对象的Clone()方法通过java.io.ObjectInputStream类的getObject()方法反序列化
实例化步骤
在堆中为保存对象的实例变量分配内存为实例变量初始化为默认的初始值为实例变量赋正确的初始值有三种技术完成赋值 如果对象是clone() 创建的jvm把原实例变量中的值拷贝到新对象中如果是通过ObjectInputStream类的readObject()调用反序列化的jvm从输入流中读取的值来初始化实例变量jvm调用对象的实例化方法把对象的实例变量初始化为正确的初始值
五、卸载Unloading)
jvm实现必须具有某种自动堆存储管理策略大部分是使用垃圾收集器。如果类声明了 void finalize()方法垃圾收集器在释放实例内存前会执行这个方法。垃圾收集器自动调用的finalize()方法抛出的任何异常都将被忽略。
从jvm中卸载类型很多情况jvm中类的生命周期和对象的生命周期很相似。jvm如何判断动态装载的类型是否仍然被程序使用其判断方式和判断对象是否仍然被使用很相似。在类使用完之后如果满足下面的情况类就会被卸载
该类所有的实例都已经被回收也就是java堆中不存在该类的任何实例。加载该类的ClassLoader已经被回收。该类对应的java.lang.Class对象没有任何地方被引用无法在任何地方通过反射访问该类的方法。
如果以上三个条件全部满足jvm就会在方法区垃圾回收的时候对类进行卸载类的卸载过程其实就是在方法区中清空类信息java类的整个生命周期就结束了。如果使用启动类装载器装载的类型永远都是可触及的所以永远不会被卸载。只有使用用户定义的类装载器装载的类型才会变成不可触及才会被卸载。
六、总结
Java生命周期中对象基本上都是在jvm的堆区中创建在创建对象之前会触发类加载加载、连接、初始化当类初始化完成后根据类信息在堆区中实例化类对象初始化非静态变量、非静态代码以及默认构造方法当对象使用完之后会在合适的时候被jvm垃圾收集器回收。读完本文后我们知道对象的生命周期只是类的生命周期中使用阶段的主动引用的一种情况即实例化类对象。而类的整个生命周期则要比对象的生命周期长的多。