- new ,包括各种单例模式等,本质上都是在new
- 使用clone() ,不需要调用构造器,但是需要实现Cloneable接口
- 使用反序列化,从文件或网络中获取一个类的二进制流
- Class 的newInstance() :反射,只能调用空参构造器,全限必须是public
- Constructor 的newInstance() ,也是反射,可以调用空参、带参的构造器,对权限没有要求,所以逐渐替代了上面的反射方式
- 第三方的类库
- 首先会判断对象的类是否被加载、链接、初始化(看元空间中有没有这个类的符号引用,没有就通过双亲委派机制把了加载进来)
- 为对象分配内存
- 计算对象占用的空间大小,然后在堆中划分出一块内存给对象
- 分配内存时,如果堆空间内存规整(数据存放很规整,地址相对连续,比较好放下对象),虚拟机就采用指针碰撞法了,也就是用过的内存在一边,空闲的内存在另一边,中间放着一个指针作为分界线,分配内存时只需要把指针想空闲的那边挪动一段与对象大小相等的距离 (想要内存规整在垃圾回收时有特定的算法)
- 如果内存不规整,已使用和未使用的内存相互交错,那么虚拟机将采用空闲列表法来为对象分配内存,虚拟机会维护一个列表,记录哪些内存块可用,哪些不可以,在分配内存的时候从列表中找到一块足够大的空间划分给对象,并更新表中的内容,这种分配方式叫做 空闲列表
- 因为堆是共享的,所以存在并发安全问题,可以使用cas或者加锁的方式解决,另外每个线程可以分配一块TLAB空间,来避免共享
- 初始化分配到的空间,也就是赋默认初始化值,这样可以保证对象的属性可以在不被赋值时就能直接使用
- 设置对象头,设置对象所属的类、对象的hashCode和对象GC的信息、锁信息等数据
- 执行init方法(类的构造器的调用)进行初始化,显示初始化、代码块初始化、构造器初始化
-
对象头
- 运行时元数据:哈希值、GC分代年龄、锁状态标志、线程持有的锁、偏向时间戳、偏向线程id
- 类型指针:指向了元空间中的具体类型 (并非所有的对象都会保留类型指针)
- 如果是对象是数组,还会保留数组的长度
-
实例数据:对象真正有效的信息,包括父类和自身拥有的字段,其中,父类中定义的变量会出现在子类之前;相同级别相同宽度的字段(字节数)总是被分配到一起;如果CompactFields参数是true(默认就是true),子类的窄变量可能插到父类变量的空隙
-
对齐填充:不是必须的,也没有特别含义,仅仅起到占位符的作用
-
句柄访问,维护了一个句柄池,句柄池在堆中,记录了对象的引用和类的引用,栈帧需要通过句柄池才能访问对象以及类
-
直接指针,hotspot采用所谓方式,栈帧里的,局部变量表记录了 堆中对象的直接引用地址,堆中对象又有方法区中类的引用
-
句柄访问,需要专门维护一个句柄池,空间开销较大,而且相对于直接指针来说效率较低,但是如果对象的位置方法变化(比如发生了GC),句柄访问就只需要维护句柄池,不需要区修改栈中的数据