2021年9月13日

ut平台美女白狐_对象的创建过程

作者 adminer

假期结束后,18号开始上班,已经很忙了,最近更新也很少。

在开始MySQL的学习之前,还想写一篇文章把前面学习的知识点回顾一下,就有了今天的这篇文章。

示例

有类School,这个类中有3个成员变量:引用类型String类型的schoolName,通过显式代码块初始化;基本数据类型int型studentsNum,显式初始化;引用类型Class类型student,通过School的构造函数初始化。

我们使用main函数创建School的一个对象,那么这个过程发生了哪些事情?JVM内存中增加了什么?一起来看看吧!

public class School {    private String schoolName;    private int studentsNum = 10000;    private Student student;    {        schoolName = "清华大学";    }    public School(){        student = new Student();    }}class Student{}class Test{    public static void main(String[] args) {        School school = new School();    }}

当我们执行new School()时,进行了对象的创建,大致可以分为以下5步:

在详细了解这5个步骤之前我们再详细聊一下对象头,在synchronized锁升级过程分析的时候我们已经初步接触过它。

对象的存储布局 对象堆积空间的存储布局包括对象头(Header)、实例数据(IntanceData)、对齐填充(Padding)三个部分。

对象头

对象头包括运行时间元数据、指向元数据的指针kclass,确认该对象所属的类型。

运行时间数据(MarkWord)包括哈希值、GC代替年龄、锁定状态标志、偏向线程ID。运行时元数据的信息发生了变化,在synchronized锁的升级过程中,MarkWord在锁的状态不同。

下图显示了无锁定状态、偏向锁定、轻量级锁定、重量锁定、对象被GC标记的对象头部运行时的数据信息:

实例数据

父类的结构方法比子类先执行,因此父类变量的定义在子类前面。

对齐填充

对齐填充不是必要的,也没有现实意义,只是占位符的作用。HotSpot虚拟机要求对象的起始地址必须是8字节的整数倍,因此当对象没有满足的时候,就需要对齐填充来补全。

现在我们已经了解了对象在堆内存的布局,在之前的JVM文章中也学习了虚拟机栈结构和方法区(JDK1.8之后称为元空间,勾勾之前习惯称为方法区,但是怕大家混淆后续我们都用元空间表示),那么接下来我们详细分析school对象创建的整个过程。

对象制作的步骤 对象制作是主线程的main()方法,因此在主线程的拟机栈中制作main()的栈架,main()是现在的方法。

回顾堆栈和堆栈框架。

JVM存储区域分为堆积、元空间、虚拟堆积、当地方法堆积和程序计数器5个模块。

虚拟堆栈和当地方法堆栈都属堆栈,当地方法堆栈只保存native方法堆栈信息。

虚拟堆栈的生命周期与线程的生命周期一致,随着线程的创建而创建,随着线程的破坏而破坏,因此是线程私有的存储区域。

虚拟机栈是由栈帧组成的,ut平台美女白狐栈帧中包含了局部变量表、操作数栈、动态链接、方法返回地址、附加信息。栈帧是随着方法的调用而创建的。因此,当主线程调用main()方法时,在主线程的虚拟堆栈中创建了main()堆栈帧。

main()堆栈的局部变量表包括args和school两个变量。

主线程虚拟堆栈的堆栈结构如下图所示:

main()方法需要实施school这一局部变量的实例化。

newschool()发生了什么?接下来,ut平台美女白狐我们将详细分析以前的五个步骤。

判断对象的类别是否已经加载

当虚拟机遇到new这个指令时,首先检查该指令的参数是否可以在元空间的常量池中定位为一个类别的符号引用,检查该符号引用代表的类别是否已经加载,即判断元空间中是否包含该类别的元信息。

我们通过javapp-test.clast.clas查看Test类字节代码信息:

class ;/e:/study/javerl/main/com/study/cost/cost/girl/bsp;
研究生产中的标题;研究生产中的标题;研究生产中的标题;研究生产中的标题;研究生产中的标题;研究生产中的标题;研究生产中的标题;研究生产中的标题;研究生产中的标题;研究生产中的标题;研究生产中的标题

如果没有这种类型的信息,则根据父母的委托模型加载School类型。

类加载过程:加载、连接、初始化,其中连接包括验证、准备、分析。

执行类加载的是类加载器,分为启动类加载器、扩展类加载器、应用类加载器和定制加载器。

School类是ClassPath下的文件,其类别加载是应用类别加载器,应用类别加载器根据ClassLoader包名类别找到对应的.class文件时,如果找不到该文件,ClassNotFoundexception就会出现异常,如果找到了,就进行类别加载,生成对应的Class类别。此时,元空间有School的元数据。

针对分配内存空间

接下来需要计算对象所占空间的大小,基本类型除long和double外为8个字节,byte和boolean为1个字节,char和short为2个字节,其他基本类型为4个字节,引用类型也为4个字节。

内存大小计算后,在堆积中将内存空间划分为新对象。大部分情况下,对象是在新生代的Eden区中分配,如果此时Eden区没有足够的内存空间进行分配,虚拟机将发起一次Minor GC。但是,当我们将内存分配成长的文字串和数组时,这种类型的大对象需要连续的内存空间,可以直接分配给老年人,避免Eden和2个s区域发生大量的内存复制。但是大对象可能会导致连续空间不足而提前触发GC,ut美女主播香草我们开发中也应该尽量避免大对象。

内存分配有两种方式:指针碰撞和空闲列表分配。

指针冲突:存储器使用的GC算法在标记整理和复印算法时,存储器是整齐的。此时,我们只需要将存储器移动到指针的位置来分配给对象。Serial和ParNew使用的GC回收算法是标记复制算法,内存的分配就是指针碰撞的方式。

空闲列表分配:当内存使用的GC算法是标记清除算法时,内存是规整的,这个时候维护了内存空闲的列表,在为新对象分。
配备存储器时,从空闲列表中找到存储器即可。CMS使用的GC回收算法是标记清除算法,内存的分配方式是空闲列表的分配。

看完内存分配有疑问吗?堆内存是所有线程共享的,如果两个线程同时都想占用这一块内存空间怎么办呢?这关系到分配内存空间时的并发安全问题。

JVM提供了两种处理和安全的方法:一种是我们常用的CAS失败再试验区域锁,保证存储器分配的原子性,另一种是打开-XX:UseTLAB数是每个线程的预分配

经过这个步骤,存储器中有School实例的存储器区域:

初始存储器空间

属性的赋值操作分为三种类型,例如:

初始存储器初始存间

不要混淆这一步的初始化和类加载过程中的初始化!

类加载过程中的初始化是对类的静态变量初始化,不包含类的实例变量。

实施此步骤后,存储器中的情况如下:

设置对象头

将对象的类别、对象的HashCode值、对象的GC信息、锁定信息等数据存储在对象头上。ut平台美女白狐它取决于JVM实现。对方头部的信息在我们面前已经说过了,在这里不再说明。ut平台美女白狐

执行了这一步之后内存中的数据变化:

执行init进行初始化

这个时候初始化过程才真正开始。该过程与字节代码invokespecial相对应,执行init方法。

实行实例代码块、调用类结构方法,将堆积物的第一个地址赋予引用变量。这一步之后,真正可用的对象完成了。

实施此步骤后,存储器的变化如下:

总结 对象的整个创建过程必须了解JVM的存储区域,熟悉各区域存储的数据,了解在哪个过程中存储的数据。

类元数据的加载是元空间的数据来源,我们还可以回顾下类加载机制、双亲委派模型、哪些场景下需要打破双亲委派,之前勾勾分析了JDBC的SPI机制,利用线程上下文类加载器打破双亲委派。

对象的创建都是基于堆空间的,ut平台美女白狐我们可以回顾下堆空间的内存分配、GC回收算法和GC回收器。

设置对象头的信息需要理解对象头,根据对象头的数据变化可以回顾synchronized锁的升级过程。

对象创建后内存的数据变化如下: