一、java对象的生命周期
java对象的生命周期可以分为7个阶段:创建阶段、使用阶段、不可视阶段、不可达阶段、可收集阶段、终结阶段、释放阶段。
1、创建阶段
java创建一个对象的方式:
(1)使用new关键字。
(2)使用反射机制。
(3)对象clone。Object类中存在clone(),但访问权限为protected,因此被clone的类需要实现Cloneable接口,将方法权限提升为public。Cloneable只是一个标识接口。
(4)使用序列化。
2、使用阶段
四类对象引用:强引用、软引用、弱引用、虚引用。
(1)强引用:强引用对象无论如何都不会被回收。当出现内存不足的情况,宁愿抛出内存溢出(OutOfMemoryError)错误。
(2)软引用:具有与强引用相同的引用功能,只有当JVM出现内存不足时,对象会被回收。
(3)弱引用:无论内存是否充足,对象都会被GC回收。
(4)虚引用:主要用于辅助finalize函数的使用,标识那些处于不可达,但是却未被GC回收的对象。
3、不可视阶段
对象使用已经结束,并且在其可视区域不再使用。此时应主动将对象置为null,有助于JVM及时发现该垃圾对象。
4、不可达阶段
JVM通过可达性分析,从root集合中找不到该对象直接或间接的强引用。此时该对象被标注为GC回收的预备对象,但没有被直接回收。
在可达性分析时可作为root的对象:
(1)被启动类(bootstrap 加载器)加载的类和创建的对象;
(2)栈(本地方法栈和java栈)引用的对象。
(3)方法区中静态引用指向的对象。
(4)方法区中常量引用指向的对象。
5、可收集阶段
GC已经发现该对象不可达。
6、终结阶段
finalize方法已经被执行
7、释放阶段
对象空间已经被重用
二、java类的生命周期
java类的生命周期分为7个阶段:加载、验证、准备、解析、初始化、使用、卸载。
(java类声明周期)
(类的各个状态)
类加载概念:类的加载指的是将.class文件的二进制数据加载到内存中,将其放置在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。
加载、验证、准备、解析、初始化五个阶段都被包含在类的加载过程中。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。
实际上,验证、准备、解析可以被划分为连接阶段。
1、类加载阶段
3个核心问题:什么时候加载?从哪里加载?怎样加载?
(1)类加载时机:虚拟机规范中没有强制规定类何时被加载,并允许类加载器在预料到某个类将要被使用到时预先加载类。如果在加载过程中遇到错误,类加载器必须在程序主动使用该类时报告该错误。如果该类一直没有被程序主动使用,类加载器就不会报告此错误。
(2)类的加载来源:本地磁盘、网络资源(.class文件)、数据库、压缩包(jar、zar)、其他文件(如jsp生成的)。
(3)类加载细节:JVM会做三件事情
类的全限定名获取该类的二进制字节流。
2 >> 将这个字节流所代表的静态存储结构转换为方法区中的运行时数据结构。
3 >> 在堆中生成一个代表该类的Class对象,作为方法区中这些数据的访问入口。
类加载阶段的工作是由类加载亲完成,java中类加载器加载类使用的策略是双亲委派机制。
2、验证阶段(连接步骤1)
验证阶段主要是确保被加载类的正确性,主要从四个方面进行验证。验证阶段是一个很重要但是非必需的阶段,因为验证同样花费时间,我们可以使用-Xverfity:none来关闭大部分的验证。
(1)文件格式的验证:验证.class文件字节流是否符合class文件的格式的规范,并且能够被当前版本的虚拟机处理。这里面主要对魔数、主版本号、常量池等等的校验(魔数、主版本号都是.class文件里面包含的数据信息、在这里可以不用理解)。
(2)元数据验证:主要是对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求,比如说验证这个类是不是有父类,类中的字段方法是不是和父类冲突等等。
(3)字节码验证:这是整个验证过程最复杂的阶段,主要是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。在元数据验证阶段对数据类型做出验证后,这个阶段主要对类的方法做出分析,保证类的方法在运行时不会做出危害虚拟机安全的事。
(4)符号引用验证:它是验证的最后一个阶段,发生在虚拟机将符号引用转化为直接引用的时候。主要是对类自身以外的信息进行校验。目的是确保解析动作能够完成。
3、准备阶段(连接步骤2)
准备阶段主要是为类变量分配内存并设置初始值。这些内存都在方法区分配。
(1)类变量(static)会分配内存,但是实例变量不会,实例变量主要随着对象的实例化一块分配到java堆中。
(2)为类变量设置初始值。这里的初始值指的是数据类型默认值,而不是代码中被显示赋予的值。感觉原理和实例变量的初始化相同,只不过是工作在不同阶段。实例变量是在为对象分配堆空间时被分配默认值,在初始化后才是代码中被显示赋予的值。但是static final 不遵守该规则。在准备阶段static final类型的值就是在代码中被显示赋予的值。
4、解析(连接步骤2)
解析阶段主要是虚拟机将常量池中的符号引用转化为直接引用的过程。
(1)符号引用:以一组符号来描述所引用的目标。
(2)直接引用:直接引用是可以指向目标的指针、相对偏移量或者是一个能直接或间接定位到目标的句柄。和虚拟机实现的内存有关,不同的虚拟机直接引用一般不同。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行
5、初始化
在该阶段才开始真正执行java代码。这个阶段就是执行类构造器< clinit >()方法的过程。
JVM初始化步骤:
(1)假如这个类还没有被加载和连接,则程序先加载并连接该类。
(2)假如该类的直接父类还没有被初始化,则先初始化其直接父类。
(3)假如类中有初始化语句,则系统依次执行这些初始化语句。
类初始化的触发:
(1)创建类的实例,使用new关键字
(2)访问某个类或接口的静态变量,或者对该静态变量赋值
(3)调用类的静态方法。
(4)反射。
(5)初始化某个类的子类,则其父类也会被初始化
(6)Java虚拟机启动时被标明为启动类的类( JavaTest),直接使用 java.exe命令来运行某个主类(含有main方法的类)
两种反射触发类加载机制的区别:
(1)通过Class.forName()方法动态加载,会默认执行初始化块(static{}),但是Class.forName(name,initialize,loader)中的initialze可指定是否要执行初始化块。
(2)通过ClassLoader.loadClass()方法动态加载,不会执行初始化块。
6、类的使用
肯定就是通过类实例化对象。
7、类的卸载
在类使用完之后,如果满足下面的情况,类就会被卸载:
(1)该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
(2)加载该类的ClassLoader已经被回收
(3)该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。
参考:
https://baijiahao.baidu.com/s?id=1636309817155065432&wfr=spider&for=pc(java类加载机制)