导读
- 移动开发知识体系总章(Java基础、Android、Flutter)
- Java四大引用类型
- 对象的生命周期
- 类的加载机制
- 类的生命周期
- 类加载器
对象的生命周期
对象的整个生命周期大致可以分为7个阶段:
- 创建阶段(Creation)
- 应用阶段(In Use)
- 不可视阶段(Invisible)
- 不可达阶段(Unreachable)
- 可收集阶段(Collected)
- 终结阶段(Finalized)
- 对象空间重分配阶段(De-allocated)
创建阶段(Creation)
一个Java类至少有一个父类Object(除了Object类本身),这个规则既是强制的,也是隐式的。你可能已经注意到在创建一个Java类的时候,并没有显式地声明扩展(extends)一个Object父类。
//TempA的声明等同于TempB
public class TempA { }
public class TempB extends java.lang.Object { }
在创建阶段系统通过以下的几个步骤来完成对象的创建过程:
- 为对象分配存储空间
- 开始构造对象
- 从超类到子类对static成员进行初始化
- 超类成员变量按顺序初始化,递归调用超类的构造方法
- 子类成员变量按顺序初始化,子类构造方法调用
一旦对象被创建,并被分派给某些变量赋值,这个对象的状态就切换到了应用阶段
应用阶段(Using)
对象至少被一个强引用持有着。
不可视阶段(Invisible)
当一个对象处于不可见阶段时,说明程序本身不再持有该对象的任何强引用,但是这些引用可能还存在着,**一般具体是指程序的执行已经超过该对象的作用域了。
boolean bool = false;
if(bool){
int count =0;
count++;
}
System.out.println(count);
本地变量count在System.out.println(count)
时已经超出了其作用域,则在此时称之为count处于不可视阶段。当然这样的情况编译器在编译的过程中会直接报错了。
不可达阶段(Unreachable)
对象处于不可达阶段是指该对象不再被任何强引用所持有,该对象仍可能被JVM等系统下的某些已装载的静态变量或线程或JNI等强引用持有着,这些特殊的强引用被称为”GC root”。存在着这些GC root会导致对象的内存泄露情况,无法被回收。
可收集阶段(Collected)
当垃圾回收器发现该对象已经处于“不可达阶段”而且垃圾回收器已经对该对象的内存空间又一次分配做好准备时,则对象进入了“收集阶段”。假设该对象已经重写了finalize()方法,则会去运行该方法的终端操作。
这里要特别说明一下:不要重载finazlie()方法!原因有两点:
- 会影响JVM的对象分配与回收速度
在分配该对象时,JVM须要在垃圾回收器上注冊该对象,以便在回收时可以运行该重载方法;在该方法的运行时须要消耗CPU时间且在运行完该方法后才会又一次运行回收操作,即至少须要垃圾回收器对该对象运行两次GC。 - 可能造成该对象的再次“复活”
在finalize()方法中,假设有其他的强引用再次持有该对象,则会导致对象的状态由“收集阶段”又又一次变为“应用阶段”。这个已经破坏了Java对象的生命周期进程,且“复活”的对象不利用兴许的代码管理。
终结阶段(Finalized)
当对象运行完finalize()方法后仍然处于不可达状态时,则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行回收。
对象空间重分配阶段(De-allocated)
对象空间又一次分配阶段,垃圾回收器对该对象的所占用的内存空间进行回收或者再分配了,则该对象彻底消失了,称之为“对象空间又一次分配阶段”。
类的加载机制
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误
加载.class文件的方式
- 从本地系统中直接加载
- 通过网络下载.class文件
- 从zip,jar等归档文件中加载.class文件
- 从专有数据库中提取.class文件
- 将Java源文件动态编译为.class文件
类的生命周期
Java虚拟机为Java程序提供运行时环境,其中一项重要的任务就是管理类和对象的生命周期。类的生命周期从类被加载、连接和初始化开始,到类被卸载结束
。当类处于生命周期中时,它的二级制数据位于方法区内,在堆区中还会有一个相应的描述这个类的Class对象(当Java程序使用任何一个类时,系统都会为之创建一个java.lang.Class对象)。只有当类处于生命周期中时,Java程序才能使用它,比如调用类的静态成员或者创建类的实例。
当Java程序需要使用某个类时,Java虚拟机会确保这个类已经被加载、连接和初始化。其中连接过程又包括验证、准备和解析这三个子步骤。这些步骤必须严格按照以下顺序执行。
加载:查找并加载类的二进制数据
查找并加载类的二进制数据加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:
- 通过一个类的全限定名来获取其定义的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。
加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。
连接:包括验证、准备和解析类的二进制数据
验证:确保被加载类的正确性
当类被加载后,就进入验证阶段。连接就是把已经读入到内存中的类的二进制数据合并到JVM运行时环境中去。连接的第一步是类的验证,其目的是保证被加载的类由正确的内部结构,并且与其他类协调一致。如果JVM检查到错误,那么就会抛出相应的Error对象。
准备:为类的静态变量分配内存,并将其初始化为默认值
解析:把类中的符号引用转换成直接引用
初始化
如果一个类被直接引用,就会触发类的初始化
。在java中,直接引用的情况有:
- 通过new关键字实例化对象、读取或设置类的静态变量、调用类的静态方法。
- 通过反射方式执行以上三种行为。
- 初始化子类的时候,会触发父类的初始化。
- 作为程序入口直接运行时(也就是直接调用main方法)。
- 除了以上四种情况,其他使用类的方式叫做被动引用,而被动引用不会触发类的初始化。
卸载
在类使用完之后,如果满足下面的情况,类就会被卸载:
- 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收
- 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射- 访问该类的方法
jvm(java虚拟机)中的几个比较重要的内存区域
- 方法区:在java的虚拟机中有一块专门用来存放已经加载的类信息、常量、静态变量以及方法代码的内存区域,叫做方法区。
- 常量池:常量池是方法区的一部分,主要用来存放常量和类中的符号引用等信息。
- 堆区:用于存放类的对象实例。
- 栈区:也叫java虚拟机栈,是由一个一个的栈帧组成的后进先出的栈式结构,栈桢中存放方法运行时产生的局部变量、方法出口等信息。当调用一个方法时,虚拟机栈中就会创建一个栈帧存放这些数据,当方法调用完成时,栈帧消失,如果方法中调用了其他方法,则继续在栈顶创建新的栈桢。
类加载器(Calssloader)
未完待续。。。
文献
https://www.cnblogs.com/9513-/p/8456877.html
https://www.cnblogs.com/wangxilei/p/9617637.html
https://www.cnblogs.com/damon9094/p/8881185.html
https://blog.csdn.net/m0_38075425/article/details/81627349