JVM基础回顾——new对象的创建过程
第一步: 类加载的检查
检查指令的参数在常量池中是否能找对应的符号引用,同时检查该符号引用对应的类是否已经被加载、解析、初始化;
第二步:为新生对象分配内存
分配对象内存的策略:
1)指针碰撞:
要求Java堆的内存是绝对工整的,所有使用过的内存放在一边,未使用过的内存放在另一边,中间有一个指针作为分界的指示器,
当需要给一个新的对象分配内存时,只需要将该指针沿着空闲内存方向移动和对象内存大小相等的距离即可;
对应的GC回收策略——有空间压缩能力:
复制算法(mark-copy):常用于新生代(朝生夕死)
——Serial;ParNew;Parallel Scavenge;G1
标记压缩算法(mark-compact):常用于老年代
——Serial Old;Parallel Old;G1
2)空闲列表
适用于Java堆的内存不是工整的,JVM维护一个空闲列表,记录了空闲内存的地址,给对象分配内存的时候,从中找一块足够大的内存空间分配给对象实例,同时更新列表记录;
对应的GC回收策略——无空间压缩能力:
标记清除算法(mark-sweep):
——CMS(Concurrent Mark Sweep);
第三步:解决并发创建对象时的线程安全问题
可行策略:
1)对分配内存的动作进行同步:
JVM采用CAS+失败重试 ==》 保证更新操作的原子性;
——CAS(Compare and Swap/比较并替换):学习链接
——失败重试:
——原子性:
2)把分配内存的动作放在线程私有的空间进行:
使用本地线程分配缓冲区(TLAB/Thread Local Allocation
Buffer);
目前JVM的策略是:
先使用TLAB,只用当TLAB用完后,才使用同步策略;
第四步:初始化分配的内存空间
保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,使程序能访问到这些字段的数据类型所对应的零值。
目的
为了实现最小安全性(即线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值);
因此JVM在堆上分配对象时,首先对内存空间进行清零,然后在堆中分配对象;为了实现多线程安全,JVM内部会保证这两个操作同步进行;
第五步:设置对象头
对象头:
1)Mark Word:存储对象自身的运行时数据;如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等;
2)类型指针:对象指向它的类型元数据的指针,Java虚拟机通过这个指针来确定该对象是哪个类的实例。
对象的结构:
1)对象头;
2)实例数据:对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字段内容
3)对齐填充;
第六步:
执行Class文件中的()方法,对属性显示初始化,以及构造方法初始化;