对象/类生命周期

本文详细阐述了Java中对象的创建过程,包括类加载、内存分配、零值初始化、构造函数执行等步骤,以及对象从创建到销毁的生命周期阶段。同时介绍了类的生命周期,包括加载、验证、准备、解析、初始化、使用和卸载。强调了对象在堆中分配内存可能采用的指针碰撞和空闲列表两种方式,以及线程安全问题的解决方案。

java new对象的创建过程

在这里插入图片描述
  对象的创建过程一般是从new指令开始的,JVM首先对符号引用进行解析,如果找不到对应的符号引用,那么这个类还没有被加载,因此JVM便会进行类加载过程。符号引用解析完毕之后,JVM会为对象在堆中分配内存,HotSpot虚拟机实现的JAVA对象包括三个部分:对象头、实例字段和对齐填充字段,其中实例字段包括自身定义的和从父类继承下来的。
  为对象分配完堆内存之后,JVM会将该内存(除了对象头区域)进行零值初始化。最后,JVM会调用对象的构造函数,调用顺序会一直上溯到Object类。

  当 JVM 遇到一条字节码 new 指令时,首先将检查该指令的参数能否在常量池中定位到一个类的符号引用,并检查这个引用代表的类是否已被加载、解析和初始化,如果没有就必须先执行类加载过程。
  在类加载检查通过后虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定,分配空间的任务实际上等于把一块确定大小的内存块从 Java 堆中划分出来。假设 Java 堆内存是绝对规整的,所有被使用过的内存都被放在一边,空闲的内存被放在另一边,中间放着一个指针作为分界点指示器,分配内存就是把该指针向空闲方向挪动一段与对象大小相等的距离,这种方式叫"指针碰撞"。
  如果 Java 堆中的内存不是规整的,那么虚拟机就必须维护一个列表记录哪些内存块是可用的,在分配时从列表中找到一块足够大的空间划分给对象实例并更新列表上的记录,这种方式叫做"空闲列表"。
  选择哪种分配方式由堆是否规整决定,堆是否规整又由所用垃圾回收器是否带有空间压缩整理能力决定。因此使用 Serial、ParNew 等带压缩整理的收集器时,系统采用指针碰撞;当使用 CMS 这种基于清除算法的垃圾收集器时,理论上只能采用空间列表分配内存。
  分配内存的线程安全问题:对象创建在虚拟机中十分频繁,即使修改一个指针所指向的位置在并发情况下也不是线程安全的,可能出现正给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针来分配内存的情况。解决该问题有两个方法:① 虚拟机采用 CAS 加失败重试的方式保证更新操作的原子性。② 把内存分配的动作按照线程划分在不同空间进行,即每个线程在 Java 堆中预先分配一小块内存,叫做本地线程分配缓冲 TLAB,哪个线程要分配内存就在对应的 TLAB 分配,只有 TLAB 用完了分配新缓冲区时才需要同步。
  内存分配完成后虚拟机必须将分配到的内存空间(不包括对象头)都初始化为零值,保证对象的实例字段在 Java 代码中可以不赋初始值就直接使用,使程序能访问到这些字段的数据类型对应的零值。之后虚拟机还要对对象进行必要设置,例如对象是哪个类的实例、如何找到类的元数据信息等。
  至此从虚拟机的视角来看一个新的对象已经产生了,但从程序的角度来说对象创建才刚开始。此时构造方法,即 Class 文件中的 init 方法还没有执行,所有字段都为默认零值,对象需要的其他资源和状态信息也还没有按照预定的意图构造好。一般来说 new 指令后会接着执行 init 方法,按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全被构造出来。

Java对象/类的生命周期

  对象的整个生命周期大致可以分为7个阶段:创建阶段、应用阶段、不可视阶段、不可到达阶段、可收集阶段、终结阶段与释放阶段。
1.创建阶段
在对象创建阶段,系统要通过下面的步骤,完成对象的创建过程:
(1)为对象分配存储空间
(2)开始构造对象
(3)递归调用其超类的构造方法
(4)进行对象实例初始化与变量初始化
(5)执行构造方法体

2.应用阶段
当对象的创建阶段结束之后,该对象通常就会进入对象的应用阶段。对象至少被一个强引用持有着

3.不可见阶段
在一个对象经历了应用阶段之后,那么该对象便处于不可视阶段,说明我们在其他区域的代码中已经不可以再引用它,其强引用已经消失

4.不可达阶段
处于不可到达阶段的对象,在虚拟机所管理的对象引用根集合中再也找不到直接或间接的强引用,这些对象通常是指所有线程栈中的临时变量,所有已装载的类的静态变量或者对本地代码接口(JNI)的引用。这些对象都是要被垃圾回收器回收的预备对象,但此时该对象并不能被垃圾回收器直接回收。

5.收集阶段
当垃圾回收器发现该对象已经处于“不可达阶段”而且垃圾回收器已经对该对象的内存空间又一次分配做好准备时,则对象进入了“收集阶段”。

6.终结阶段
当对象运行完finalize()方法后仍然处于不可达状态时,则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行回收。

7.释放阶段
垃圾回收器对该对象的所占用的内存空间进行回收或者再分配了,则该对象彻底消失了。

类的完整生命周期包括7个部分:加载、验证、准备、解析、初始化、使用、卸载。其中,验证、准备、解析称为连接阶段,除了解析外,其他阶段是顺序发生的,而解析可以与这些阶段交叉进行,因为Java支持动态绑定,需要运行时才能确定具体类型;在使用阶段实例化对象。
在这里插入图片描述
加载
加载阶段,虚拟机需要完成三件事:通过类名字获取类的二进制字节流——将字节流的内容转存到方法区——在内存中生成一个Class对象作为该类方法区数据的访问入口。其中,通过类名获取类的二进制字节流是通过类加载器来完成的。其加载过程使用“双亲委派模型”

验证
当一个类被加载之后,必须要验证一下这个类是否合法,保证加载的类是能够被jvm所运行,比如这个类是不是符合字节码的格式、变量与方法是不是有重复、数据类型是不是有效、继承与实现是否合乎标准等等。

准备
为类变量(静态变量)在方法区分配内存,并设置零值。注意:这里是类变量,不是实例变量,实例变量是对象分配到堆内存时根据运行时动态生成的。

解析
把常量池中的符号引用解析为直接引用:根据符号引用所作的描述,在内存中找到符合描述的目标并把目标指针返回。

初始化
如果一个类被直接引用,就会触发类的初始化。在java中,直接引用的情况有:

  • 通过new关键字实例化对象、读取或设置类的静态变量、调用类的静态方法
  • 通过反射方式执行以上三种行为
  • 初始化子类的时候,会触发父类的初始化
  • 作为程序入口直接运行时(直接调用main方法)

使用
类的使用包括主动引用和被动引用,主动引用会引起类的初始化,而被动引用不会引起类的初始化。被动引用:

  • 引用父类的静态字段,只会引起父类的初始化,而不会引起子类的初始化
  • 定义类数组,不会引起类的初始化
  • 引用类的常量,不会引起类的初始化

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

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

  对象基本上都是在jvm的堆区中创建,在创建对象之前,会触发类加载(加载、连接、初始化),当类初始化完成后,根据类信息在堆区中创建实例对象,初始化非静态变量、非静态代码以及默认构造方法,当对象使用完之后会在合适的时候被jvm垃圾收集器回收。对象的生命周期只是类的生命周期中使用阶段的主动引用的一种情况(即实例化类对象)。而类的整个生命周期则要比对象的生命周期长的多。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值