this的创建时机

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Mutou_ren/article/details/88933448

在看HashMap源码的时候,发现注解中有提到
Map m = Collections.synchronizedMap(new HashMap(...));
感到陌生去翻了下源码,发现了

public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
        return new SynchronizedMap<>(m);
    }
private static class SynchronizedMap<K,V>
        implements Map<K,V>, Serializable {
        private static final long serialVersionUID = 1978198479659022715L;

        private final Map<K,V> m;     // Backing Map
        final Object      mutex;        // Object on which to synchronize

        SynchronizedMap(Map<K,V> m) {
            this.m = Objects.requireNonNull(m);
            mutex = this;
        }

很明显SynchronizedMapCollections的一个内部类,接收一个Map创建一个获取了监视器锁的同步Map
突然发现了构造器中的mutex = this;
可以发现this在构造器的执行中便已经存在了,那么问题来了,this是什么时候创建的?
百度了一下别人的解答,查看原文

1.JVM把Person类的Class信息从硬盘加载到内存,
2.把Person类的类信息加载到方法区
3.找到main函数在栈中为main函数创建一个栈帧,并在栈中创建一个p变量,初始值为null
4.执行到“Person person =new Person();”这条语句时JVM在堆区开辟一块对象空间用于存储new Person()创建出来的这个对象,并执行隐式初始化 int型数据初始化为零,其他类型数据默认为空(注意:此时对象已经被创建好了,而this也已经产生了)
5.执行显示初始化操作,如调用构造函数进行显式初始化(注意:在执行构造方法时对象在第4步已经产生,此时的构造方法只是对对象进行显式的初始化操作)
6.将堆区new Person();创建的对象首地址赋值给person

从文章中可以看到,创建对象的真正时机应该在new关键字上。

百度一下关于new关键字的内容,参考这篇文章中的虚拟机处理new指令的流程

MyClass myClass = new MyClass();
这个关键字new,也在这个时候被虚拟机解析到了,并且在虚拟机识别到new关键字之后就会使用虚拟机提供的new指令去创建所指定的对象。它会大致经历如下过程:

校验
检查这个指令的参数是否能在常量池中定位到一个符号引用,并检查这个符号引用代表的类是否已经被虚拟机加载过、解析、和初始化过。如果没有,就必须先执行类的加载过程. 如果有,则根据关键new后紧跟的构造方法去创建一个类的对象出来。
分配内存
为新生的对象分配内存。对象所需内存大小在类加载完成后就可以完全确定。为对象分配空间的任务等同于把一块大小确定的java内存从java堆中划分出来。划分方式的详细内容可以参考深入理解java虚拟机一书。除了划分可用空间以外还需要考虑并发状态下分配内存的冲突问题,这个时候有两种解决办法:一是采用CAS配上失败重试,二是预先分配一小块的TLAB内存。
初始化
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一操作保证了对象的实例字段在Java代码中可以不赋初始值就可以直接使用。
设置
接下来虚拟机要对对象进行必要的设置,例如对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、GC的对象分代年龄等信息。这新信息储存在对象头中(Object Header)之中
真正按程序员意愿初始化赋值。在上面的工作都完成后,从虚拟机的角度来看,一个新的对象已经产生,但从Java的角度来看,对象的创建才刚刚开始——方法还没有执行,所有的字段还都为零。因此在执行完new指令之后会接着执行方法,把对象按照程序员的意愿进行初始化,这样一个真正开用的对象才算创建完成。所谓程序员的意愿,就是我们new对象的时候指定的构造方法,如上面的默认构造方法:
MyClass myClass = new MyClass();
同时,在这个步骤我们必须要注意到的一点就是,实例变量在这一步已经初始化了,因此我们直接使用编译不会出错。而局部变量我们在编程时就会发现,只是定义一个变量不进行初始化是会报编译错误的,原因是,局部变量必须经过显示初始化之后才能使用,系统不会为局部变量执行初始化。定义了局部变量以后,系统并没有给局部变量进行初始化,直到程序给这个局部变量赋给初值时,系统才会为这个局部变量分配内存空间,并将初始值保存到这块内存中。

总结:虽然有学习过JVM的类加载机制,但还是没有融会贯通。

参考:

  1. https://blog.csdn.net/eE1224/article/details/82457904
  2. https://blog.csdn.net/topdeveloperr/article/details/81194654#虚拟机处理new指令的流程

猜你喜欢

转载自blog.csdn.net/Mutou_ren/article/details/88933448