细说jvm(二)、java对象创建过程

简介: 细说jvm(二)、java对象创建过程

上篇大体说了jvm运行时候的数据区域,这篇我们来说说对象创建的过程。注意哈,这篇文章应该是对你写高性能的代码是有帮助的。

对象真的是振奋单身狗们心灵的一个词,在面向对象编程里面就更爽了,想new多少就new多少,想new什么样的就new什么样的。

好了,说正经的,对象创建是我们编程中做的非常频繁的一件事情,那么对象创建都经历了什么过程呢?我们这篇里面仅仅说遇到new指令时候所进行的操作。

我们以下面这段代码为例


// 这个类仅仅有个main方法,main方法仅仅new了个对象而已
public class TestNew {
    public static void main(String[] args) {
        new MyEntity(1,"a");
    }
}


MyEntity.java


@Setter
@Getter
// 仅仅是两个属性以及空参和无参构造
public class MyEntity {
    private Integer id;
    private String name;
    public MyEntity(Integer id) {
        this.id = id;
    }
    public MyEntity(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}


我们不多BB,直接javap -v TestNew.class,来看main方法字节码(别害怕,我一句一句解释给你)

如下是main方法对应字节码


1686814320545.png


为了方便讲解,我把它粘了下来:


// 注意哈,你需要重点关注的只有我注释的5-12的地方,其他的跟着看一遍就行
// 注意哈,你需要重点关注的只有我注释的5-12的地方,其他的跟着看一遍就行
// 注意哈,你需要重点关注的只有我注释的5-12的地方,其他的跟着看一遍就行   重要的事情说三遍
public static void main(java.lang.String[]);
    // 1. 这个descriptor 描述的是方法的参数是string类型的数组,最后这个大写的V表示是个void的返回值类型
    descriptor: ([Ljava/lang/String;)V
    // 2. 方法描述符,下面的两个值说明是个public 的 static 方法
    flags: ACC_PUBLIC, ACC_STATIC
    // 3. Code表示是方法的代码部分
    Code:
    // 4.栈桢中的操作数栈的深度是4,局部变量个数是1,方法参数的个数是1
      stack=4, locals=1, args_size=1
      // 5.遇到了一条new指令,然后创建了一个空的对象,执行默认的初始化逻辑(这里没有为成员属性赋值)
         0: new           #2                  // class com/example/demo/asmtest/MyEntity
         // 6. 把对象的引用压到操作数栈的栈顶
         3: dup
         // 7. 把int类型值为1的这个数压到操作数栈栈顶
         4: iconst_1
         // 8. 给1执行了一下Integer.valueof方法,这个因为id类型是Integer,但你给的1实际是int
         5: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         // 9. 把字符串”a“从常量池压到栈顶,ldc这个指令是把int,float,string类型的值从常量池加载到栈顶
         8: ldc           #4                  // String a
         // 10. 调用MyEntity的构造方法。为什么是这时候调用?因为需要的两个变量这时候已经准备好了
        10: invokespecial #5                  // Method com/example/demo/asmtest/MyEntity."<init>":(Ljava/lang/Integer;Ljava/lang/String;)V
         // 11. 上一步创建出来的对象由于没有用变量去接收,所以直接用pop指令弹出
        13: pop
        // 12. 方法结束返回到调用它的地方
        14: return
        // 13. LineNumberTable是方法的行号表,这是调试用的
      LineNumberTable:
        line 8: 0
        line 9: 14
        // 14。 LocalVariableTable是局部变量表,也是属于调试信息
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  args   [Ljava/lang/String;
        // 15. MethodParameters是方法参数表
    MethodParameters:
      Name                           Flags
      args


我们一点一点的来说,第5句这里其实做了很多事情,大概如下:

  1. 首先检查这个new指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、验证、准备、解析、和初始化过。如果没有那就表明该类还没有被虚拟机加载无法创建,需要先执行类的加载过程(这里这个类其实就是MyEntity)。
  2. 类加载检查之后,jvm在java堆中按照 “指针碰撞” 或者 “空闲列表” 的方式为新生的对象分配内存,分配内存的大小在类加载完成后便可完全确定(由于我们使用的垃圾回收器是CMS,所以是按照空闲列表的方式来的)。
  3. 内存分配完成之后,jvm需要将分配到的内存空间进行初始化零值(不包含对象头),该步骤保证了对象的实例字段在Java代码中可以不赋初始值就可以直接使用,程序能访问到这些字段的数据类型所对应的零值。(实例变量无需初始化即可被程序使用,区别于局部变量的必须初始化才能被程序使用)
  4. 接下来对对象进行必要的设置:这个对象是哪个类的实例、如何找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息、这些信息存放于对象的对象头信息中。

到这里new指令做的事情就完了,接下来要填充对象实例了,也就是该给成员属性赋值了,但是成员属性的值也需要一个准备过程,你肯定觉得很奇怪,为什么还需要准备呢?我不是已经在new MyEntity(1,“a”)中把值给了吗?这是因为你给的1其实是int类型,但是id这个成员属性其实是Integer类型,因此需要装箱,因此就有了8这里这个,然后“a”是在字符串常量池的,需要从常量池中把引用加载到栈顶来,也就是9这里的操作,这里都准备好了,才在10调用了MyEntity类的构造方法初始化成员变量。10这里做的事情可以概括如下:

5.开始执行方法进行对象的初始化,按照程序猿的意愿初始化对象,到这里一个成品的对象就算是加载完了

我上面说的12345就是对象创建的完整过程,细节你可以对着字节码的看看,下一篇说对象创建过程的内存分配。

有什么问题不明白也可以在评论区问我,我并非没耐心的人,只是比较忙,你问的问题我看到的话一定会认真给你解释。

目录
相关文章
|
5月前
|
监控 Java Unix
6个Java 工具,轻松分析定位 JVM 问题 !
本文介绍了如何使用 JDK 自带工具查看和分析 JVM 的运行情况。通过编写一段测试代码(启动 10 个死循环线程,分配大量内存),结合常用工具如 `jps`、`jinfo`、`jstat`、`jstack`、`jvisualvm` 和 `jcmd` 等,详细展示了 JVM 参数配置、内存使用、线程状态及 GC 情况的监控方法。同时指出了一些常见问题,例如参数设置错误导致的内存异常,并通过实例说明了如何排查和解决。最后附上了官方文档链接,方便进一步学习。
642 4
|
1月前
|
安全 Oracle Java
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
189 0
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
|
2月前
|
存储 运维 Kubernetes
Java启动参数JVM_OPTS="-Xms512m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError"
本文介绍了Java虚拟机(JVM)常用启动参数配置,包括设置初始堆内存(-Xms512m)、最大堆内存(-Xmx1024m)及内存溢出时生成堆转储文件(-XX:+HeapDumpOnOutOfMemoryError),用于性能调优与故障排查。
314 0
|
4月前
|
存储 监控 算法
Java程序员必学:JVM架构完全解读
Java 虚拟机(JVM)是 Java 编程的核心,深入理解其架构对开发者意义重大。本文详细解读 JVM 架构,涵盖类加载器子系统、运行时数据区等核心组件,剖析类加载机制,包括加载阶段、双亲委派模型等内容。阐述内存管理原理,介绍垃圾回收算法与常见回收器,并结合案例讲解调优策略。还分享 JVM 性能瓶颈识别与调优方法,分析 Java 语言特性对性能的影响,给出数据结构选择、I/O 操作及并发同步处理的优化技巧,同时探讨 JVM 安全模型与错误处理机制,助力开发者提升编程能力与程序性能。
Java程序员必学:JVM架构完全解读
|
4月前
|
Java
深入JavaSE:详解Java对象的比较。
总的来说,Java对象的比较就像海洋生物的比较,有外在的,有内在的,有面对所有情况的,也有针对特殊情况的。理解并掌握这些比较方式,就能更好地驾驭Java的世界,游刃有余地操作Java对象。
83 12
|
4月前
|
Arthas 监控 Java
Arthas vmtool(从 jvm 里查询对象,执行 forceGc)
Arthas vmtool(从 jvm 里查询对象,执行 forceGc)
334 16
|
5月前
|
编解码 JavaScript 前端开发
【Java进阶】详解JavaScript的BOM(浏览器对象模型)
总的来说,BOM提供了一种方式来与浏览器进行交互。通过BOM,你可以操作窗口、获取URL、操作历史、访问HTML文档、获取浏览器信息和屏幕信息等。虽然BOM并没有正式的标准,但大多数现代浏览器都实现了相似的功能,因此,你可以放心地在你的JavaScript代码中使用BOM。
161 23
|
5月前
|
Java 数据安全/隐私保护
Java 类和对象
本文介绍了Java编程中类和对象的基础知识,作为面向对象编程(OOP)的核心概念。类是对象的蓝图,定义实体类型;对象是具体实例,包含状态和行为。通过示例展示了如何创建表示汽车的类及其实例,并说明了构造函数、字段和方法的作用。同时,文章还探讨了访问修饰符的使用,强调封装的重要性,如通过getter和setter控制字段访问。最后总结了类与对象的关系及其在Java中的应用,并建议进一步学习继承等概念。
122 1
|
5月前
|
存储 缓存 Java
理解Java引用数据类型:它们都是对象引用
本文深入探讨了Java中引用数据类型的本质及其相关特性。引用变量存储的是对象的内存地址而非对象本身,类似房子的地址而非房子本身。文章通过实例解析了引用赋值、比较(`==`与`equals()`的区别)以及包装类缓存机制等核心概念。此外,还介绍了Java引用类型的家族,包括类、接口、数组和枚举。理解这些内容有助于开发者避免常见错误,提升对Java内存模型的掌握,为高效编程奠定基础。
271 0
|
5月前
|
Java
java中一个接口A,以及一个实现它的类B,一个A类型的引用对象作为一个方法的参数,这个参数的类型可以是B的类型吗?
本文探讨了面向对象编程中接口与实现类的关系,以及里氏替换原则(LSP)的应用。通过示例代码展示了如何利用多态性将实现类的对象传递给接口类型的参数,满足LSP的要求。LSP确保子类能无缝替换父类或接口,不改变程序行为。接口定义了行为规范,实现类遵循此规范,从而保证了多态性和代码的可维护性。总结来说,接口与实现类的关系天然符合LSP,体现了多态性的核心思想。
117 0