一个对象在JVM里的创建过程

一、JDK、JRE、JVM的关系

JVM:JVM是面向操作系统的,它负责把程序运行编译成的.Class字节码解释成系统所能识别的指令并执行,同时也负责程序运行的内存的管理。

JRE:JRE是是面向于程序的,JRE对JVM进行了一层包装,它除了提供JVM的功能之前,还提供了一套语言需要编译成Class后在JVM内运行所依赖环境(比如说 String.class、Object.class等这种运行时必须依赖的对象)。

JDK:JDK是面向更上层的开发人员,JDK在JRE的基础上又进行了一层包装,它除了提供运行功能之外,还对开发人员提供了一套常用的开发工具和类库,来方便开发人员写代码(比如说方便部署的编译工具,方便开发的工具类等)。

二、JVM的工作流程

一个java文件从编码到执行需要经过下面几个阶段

1、编译阶段:首先.java经过javac编译成.class文件

2、加载阶段:然后.class文件经过类的加载器加载到JVM内存。

3、解释阶段:class字节码经过字节码解释器解释成系统可识别的指令码。

4、执行阶段:系统再向硬件设备发送指令码执行操作。

2.1 编译阶段

类的编译阶段主要目的是把源码文件编译成JVM可以解析的class文件,这个阶段会经过词法分析、语法语义分析、生成字节码,这个几个阶段后生成最后的class

class文件包含下面几部分内容

Magic Number(魔数): 在.class文件头4个字节(CA FE BA BE),魔数的作用就是定义一个识别标准,只有符合这个标准才能被JVM解读 。

版本号: 编译class文件的JDK版本号,此版本时向下兼容的。

常量池: 常量池中信息主要包括字面量(字符串(String a=“b”)、基本类型的常量(final 修饰的变量))和符号引用(类和接口全限定名,字段名称和描述符、方法名称和描述符,方法句柄和方法类型,动态调用点和动态常量)。

访问标志: 该类是否为接口、注释、枚举、模块,是否为final ,abstract等信息。

类索引: 类索引、父类索引、接口索引集合,用于确定类的继承和实现关系。

字段表集合:用于描述接口或者类中声明的变量信息,如修饰作用域(public,private,protected),是实例变量还是类变量(static),是否可变(final),是否可序列化(transient),并发可见性(volatile),字段数据类型,字段名。

方法表集合:方法表集合和字段表的信息类似,只不过这里保存的是方法相关的信息,包括方法的名称,访问标志(如public 、static 、final、abstract、native、sychronized等),名称索引,属性表集合(在方法内定义的属性、方面里面的代码指令)等信息。

属性表集合: 包括类、方法、实例的变量属性和代码的指令码。

2、加载阶段

加载阶段主要是把编译阶段生成的.class文件加载到JVM内存中去,这个阶段会经过装载、连接、初始化三个过程。

2.1、装载

装载阶段主要做的事情就是先把class的信息读到内存来。

(1)先通过类的全限定名读取到描述此类的二进制流。

(2)把字节流中描述静态结构的信息转化为方法区中的运行时数据结构。

(3)在Java堆中生成一个代表这个类的java.lang.Class对象,作为这个对象的访问入口

2.2、连接

连接阶段会对class的信息进行验证、为类变量分配内存空间并对其赋默认值。

(1)验证 :对class内容进行验证看字节信息是否符合JVM规范,包括元数据、字节码、符号引用的验证。

(2)准备: 为静态变量(final 和static定义的变量)分配内存空间,初始化静态变量值( int a=0, object=null的操作)。

(3)解析:把符号引用转换为直接引用,因为这里类的信息已经被加载到内存了,所以这里会把原来通过全限定名引用的对象替换成对象内存的实际地址。

2.3、初始化

初始化阶段主要是执行初始化静态块内容,并且为静态变量进行真正的赋值操作,这里JVM会通过执行编译器自动生成的<clinit>() 方法(此方法是编译器通过搜集类的static{}静态快组成的代码)而完成具体工作。

3、解释阶段

解释阶段是在代码执行的时候来触发的,当我们尝试执行一个类的方法时,首先会通过这个类的对象作为入口,找到对应方法的字节码的信息,然后解析器会把字节码信息解释成系统能识别的指令码。

解释阶段会有两种方式把字节码信息解释成机器指令码,一个是字节码解释器、一个是即时编译器JIT,一般来说当我们运行某个代码的时候会默认使用字节码解释器进行指令解析,只有当某个方法称为热点方法后,即时编译器就会把热点方法的指令码保存起来,下次方法执行的时候就无需重复的进行解析,所以JIT是对解析过程中的一种优化手段。

4、执行阶段

操作系统把解释器解析出来的指令码,调用系统的硬件执行最终的程序指令。

三、一个对象的创建过程

这里我们通过一个类的代码分别追踪类的静态变量、成员变量、在经过静态块、对象代码块的变更后,最终创建完的对象在内存中是如何标示和保存的,演示流程和演示代码如下:

public class Person {
​
    //静态变量
    public  static  int staicVariabl=1;
    //成员变量
    public   int  objVariabl;
​
    //静态初始代码块
    static {
        staicVariabl=2;
    }
​
    //对象初始化代码块
    {
        objVariabl=88;
    }
​
    //构造方法
    public Person() {
        objVariabl=99;
    }
​
    public static void main(String[] args) {
        Person person=new Person();
    }
​
}

3.1编译成class文件

jdk1.8

3.2加载

3.2.1 装载类的相关信息到JVM内存中,解析出类的描述信息会保存到Metaspace

3.3.3 连接 连接阶段会对静态变量的值进行默认赋值,此时Person类的staicVariabl 赋值为0;

3.3.4 初始化

1、首先会对Person类的静态变量staicVariabl 进行真正的赋值的操作(此时staicVariabl =1)。

2、然后收集类的静态代码块内容,生成一个类的<clinit>() 方法并执行(此时staicVariabl =2)。

4.创建对象

当我们new一个对象的时候JVM首先会去找到对应的类元信息,如果找不到意味着类信息还没有被加载,所以在对象创建的时候也可能会触发类的加载操作。当类元信息被加载之后,我们就可以通过类元信息来确定对象信息和需要申请的内存大小。

对象创建的流程

当我们执行上面代码中main方法中的的 Person person=new Person() 时,我们的对象就开始创建了,执行流程大致分为三步:

4.1、构建对象:首先main线程会在栈中申请一个自己的栈空间,然后调用main方法后会生成一个main方法的栈帧。然后执行new Person() ,这里会根据Person类元信息先确定对象的大小,向JVM堆中申请一块内存区域并构建对象,同时对Person对象成员变量信息并赋默认值。

4.2、初始化对象:然后执行对象内部生成的init方法,初始化成员变量值,同时执行搜集到的{}代码块逻辑,最后执行对象构造方法(init 方法执行完后objVariabl=88,构造方法执行完后objVariabl=99)。

4.3、引用对象:对象实例化完毕后,再把栈中的Person对象引用地址指向Person对象在堆内存中的地址。

5.对象在内存的布局

对象创建完成后在内存中保存了保存的信息包括对象头、实例数据及对齐填充三类信息。

对象头:

对象头里主要包括几类信息,分别是锁状态标志、持有锁的线程ID、GC分代年龄、对象HashCode,类元信息地址、数组长度,这里并没有对对象头里的每个信息都列出而是进行大致的分类,下面是对其中几类信息进行说明。

锁状态标志:对象的加锁状态分为无锁、偏向锁、轻量级锁、重量级锁几种标记。

持有锁的线程: 持有当前对象锁定的线程ID。

GC分代年龄: 对象每经过一次GC还存活下来了,GC年龄就加1。

类元信息地址: 可通过对象找到类元信息,用于定位对象类型。

数组长度: 当对象是数组类型的时候会记录数组的长度。

实例数据

对象实例数据才是对象的自身真正的数据,主要包括自身的成员变量信息,同时还包括实现的接口、父类的成员变量信息。

对齐填充

根据JVM规范对象申请的内存地址必须是8的倍数,换句话说对象在申请内存大小时候8字节的倍数,如果对象自身的信息大小没有达到申请的内存大小,那么这部分是对剩余部分进行填充。

Person对象最终创建完成后内存中数据情况大概如下图:

本文参考:https://zhuanlan.zhihu.com/p/142614439

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值