Java 对象从创建到毁灭的过程

Step1:类加载检查

虚拟机遇到⼀条 new 指令时,⾸先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引⽤,并且检查这个符号引⽤代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执⾏相应的类加载过程。

加载:获取二进制流,并加载到方法区,并在堆中生成一个java.lang.Class对象,作为方法区这个类的访问入口。

验证:验证字节流是否符合虚拟机的标准 ,比如开头为CA FE BA BE。

准备:为类变量(static)分配内存(注意,逻辑上的方法区,实际上的堆)和零值(初值)。

解析:把类中的符号引用转换为直接引用

初始化:执行类构造器()方法,为**类变量(static)**初始化值(自定义值)。

关于初始值的问题:

final修饰的实例属性,在实例创建的时候才会赋值。

static修饰的类属性,在类加载的准备阶段赋初值,初始化阶段赋值。

static+final修饰的String类型或者基本类型常量,JVM规范是在初始化阶段赋值,但是HotSpot VM直接在准备阶段就赋值了。

static+final修饰的其他引用类型常量,赋值步骤和第二点的流程是一样的。

参考了 :https://blog.csdn.net/Soul_wh/article/details/111409565

Step2:分配内存

类加载检查通过后,接下来虚拟机将为新⽣对象分配内存

如何分配:

在这里插入图片描述

分配在哪:

对象优先分配Eden

  • 先TLAB分配
  • 再分配Eden+CAS
  • 大对象直接分配到老年代

在这里插入图片描述

如何保证线程安全:

CAS+失败重试: CAS 是乐观锁的⼀种实现⽅式。所谓乐观锁就是,每次不加锁⽽是假设没有冲突⽽去完成某项操作,如果因为冲突失败就重试,直到成功为⽌。虚拟机采⽤ CAS配上失败重试的⽅式保证更新操作的原⼦性。

TLAB:为每⼀个线程预先在 Eden 区分配⼀块⼉私有的缓存区域,JVM 在给线程中的对象分配内存时,⾸先在 TLAB 分配,当对象⼤于 TLAB 中的剩余内存或 TLAB 的内存已⽤尽时,再采⽤上述的 CAS 进⾏内存分配。默认情况TLAB仅占每个Eden区域的1%。

Step3:初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这⼀步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使⽤,程序能访问到这些字段的数据类型所对应的零值。

Step4:设置对象头

初始化零值完成之后,虚拟机要对对象进⾏必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。

Step5:执⾏init⽅法

执⾏ new 指令之后会接着执⾏ ⽅法,把对象按照程序员的意愿进⾏初始化,这样⼀个真正可⽤的对象才算完全产⽣出来。

此时,一个对象的完整信息应该包括:

在这里插入图片描述

后面就可以开始使用了~

Step6:对象晋升(年轻代)

1.每次Eden满之后,进行YGC,并将幸存者放入S0或者S1(to空间),并赋予age属性。

2.在下一次Eden满的时候再进行YGC(包括from空间),同时将幸存者和from(上一次to)空间中的幸存者放入to空间。

3.在age>=15(阈值,可以参数设置)时,将这些对象放入老年代。

**tips:默认晋升年龄并不都是 15,这个是要区分垃圾收集器的,CMS就是6.**

4.在老年代也满的情况下怎么办,进行FGC(老年代垃圾回收),如果放不下,OOM。

FGC的触发这里并不准确的
在这里插入图片描述

Step7:垃圾回收

Java 虚拟机使用该算法来判断对象是否可被回收,使用两种方法,引用计数(过时)和可达性分析。

可达性分析通过GC Roots 进行起点进行搜索,能够到达的对象都是存活的,不可达的对象都是可回收的(不是立马回收)。

GC Roots 一般包含以下内容:

  • 虚拟机栈中引用的对象
  • 本地方法栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中的常量引用的对象

总结来说一句话:非堆中的任何引用,避免如果两个堆中的对象循环引用。

一个对象的死亡需要经历两次标记过程:

如果没有GcRoot相连接,那么它就会被第一次标记,接下来:

  1. 对象没有重写finalize方法,或者finalize方法已经被虚拟机调用 -> 不可触及(等死吧)

  2. 如果重写了finalize,但是还没执行。需要放在 F-Queue队列中等待调用 ->可复活的

  3. 如果调用了finalize方法,并且与引用链上如何一个对象建立了联系,那么第二次标记的时候,obja会被移出“即将回收”。对象的finalize方法永远只会调用一次。在调用之后,再没有引用的情况,那么对被直接会被回收(不可触及)。

一个对象的生命周期只能调用一次finalize()方法。而且该方法可以被try-finally代替,并做的更好。

参考:
《深入理解Java虚拟机》
JavaGuide:http://snailclimb.gitee.io/
尚硅谷课程


版权声明:本文为weixin_42042852原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。