STEP1:类加载检查
虚拟机遇到一条 new 指令的时候,首先去常量池中检查该对象的符号引用,并检查该引用是否被加载过、初始化过、解析过。如果没有,就要去执行类加载过程。
STEP2:分配内存
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定。分配方式有两种:”指针碰撞”和“空闲列表”两种,选择那种分配方式由Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能所决定。
指针碰撞:
适用场合:堆内存规整(没有内存碎片)的情况(复制算法,标记压缩算法)
原理:用过的内存全部整合到一边,其中用一个指针来分隔,来了一个新对象,指针往没有用过内存的地方移动。
GC 收集器:serial(标记压缩),parallel(serial 的多线程版本)
空闲列表
使用场合:堆内存不规整,有内存碎片(标记清楚算法)
原理:虚拟机会维护一个列表,该列表中会记录那些内存块是可用的,在分配的是偶,找一块足够大的内存块来创建对象实例,然后更新列表。
内存分配并发问题
在创建对象中,我们肯定不能允许另外的线程来干扰,就比如你女票被男的骚扰了,你爽吗?所以我们虚拟机在创建对象的时候要保证线程安全。通常也有两种方式来保证创建对象 是线程安全的:
CAS+失败重试:
CAS 是乐观锁的一种实现。乐观锁是指,它每次都假设没有其他线程来干扰的,如果有线程干扰,那就重新创建,直到创建成功。这样可以保证更新操作的原子性。
TLAB:
为每一个线程预先在 Eden 区域分配一块内存,首先 TLAB 分配,对象的需要的内存大于了 TLAB 提供的,再采用 CAS 进行内存分配。
STEP3:初始化零值
当内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这 一步操作保证了对象的实例字段在 Java 代码中可以不赋初值就直接使用,程序能访问到这些字段的数据类型所对应的零值。就跟有些成员变量你赋值了,有些没有赋值,那么那 些没有赋值的就是 Null 的道理是一样的。
STEP4:设置对象头
初始化完成后,我们需要一个东西去辨认我们这个新创建对象的一些信息。很多事物的基本信息都存在什么头,比如 http,它的大概属性都会存在信息头中,比如请求方式之类的。当然我们这个新创建对象也是一样的,我们就用对象头来存储对象是那个类的实例、类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。
STEP5:执行 init 方法
经过上面 4 步操作后,我们从虚拟机的角度来看,一个新的对象已经产生了,但从 Java 程序中,对象创建好了,我们都一般还有构造函数去初始化值,所以<init>方法就起作用 了,把对象按照程序员的意愿来进行初始化,这样 5 步才算把一个真正可用的对象完全产生出来。
更多JVM笔记:
https://blog.csdn.net/qq_40262372/article/details/113733631
B站视频讲解如何三个月学习JAVA拿到实习Offer:
https://www.bilibili.com/video/BV1dV411t71K
B站,VX关注:万小猿,我们会不断努力更新更多相关的知识笔记
如果想要在学习的道路上和更多的小伙伴们交流讨论
请加Q群:725936761
欢迎每一位小伙伴的加入
我们一起朝着目标加油!冲锋陷阵!