对象创建的过程

关于对象创建的过程与知识
一.创建对象的方式
有4种显式地创建对象的方式:
1.用new语句创建对象,这是最常用的创建对象的方式。
2.运用反射手段,调用Java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。
3.调用对象的clone()方法。
4.运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法.

二.new一个对象中所发生的事情
A.检查
检查这个指令的参数是否能在常量池中定位到一个类符号的引用,并且检查这个类是否已经被加载,解析,和初始化过
B.如果没有检查到,那么就需要执行类加载过程
C.为对象分配堆内存
堆内存分配方式:
1.指针碰撞:如果对象已分配的内存和未分配的内存是分开的,且以指针为界面,那么指针向未分配的地方移动类需要分配的距离
2.空闲列表:需要维护一个列表,记录那些内存是可用的,然后分配,并且标记是不可用的
划分内存存在并发问题:因为都在操作虚拟机内存,解决问题有两种
1.对分配内存进行同步处理,实际上虚拟机采用CAS和失败重试机制来分配
2.每个线程都有自己的虚拟一小块内存(TLAB--本地线程分配缓冲),当他分配完成时候 才实行同步操作
D.将分配的内存空间都初始化零值(不包括对象头),使用TLAB则提前分配,这个操作保证了对象的实例字段在不赋初值就可以直接使用
E.设置对象头
设置对象的元数据信息,对象的哈希码,对象的分代年龄
F.调用init方法,把对象按照程序员的意愿进行初始化

三.JAVA对象的内存布局
A.java对象头 Mark Word
1.头信息里面包括:哈希码,分代年龄,线程持有锁,偏向线程ID,
2.类型指针-可以称之为类标记
即指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
如果对象是一个数组,则还要记录数组的长度,但是无法确定数组的大小
B.实例数据:对象存储的有效性信息,也是程序中定义的字段内容
存储的顺序受到分配策略参数以及定义顺序的影响,相同宽度的字段总是被分配到一起,父类定义的变量会出现在子类之前
C.内存对齐
不是必然存在的,起着占位符的作用,HotSpot VM要求对象起始地址必须是8字节的整数倍,如果对象实例数据部分没有对齐,则需要通过对齐填充来补全

四.对象的访问定位
通过栈上的reference来操作堆上的具体数据,也分为两种
1.使用句柄访问,那么堆中需要一块区域作为句柄池,对象实例数据可以直接访问 对象类型数据则需要句柄指针指向


五.CAS
简单来说:从某一内存上取值V,和预期值A进行比较,如果内存值V和预期值A的结果相等,那么我们就把新值B更新到内存,如果不相等,那么就重复上述操作直到成功为止

问题:
1、首先就是经典的ABA问题
何为ABA呢?我们还是以两个线程L、N进行自增操作为例,线程L、N同时获取当前的值A,
只不过此时线程N比较快,它在L操作之前,进行了两次操作,第一次将值从A 改为了B,之后又将B改为了A,那么在线程L操作的 时候发现当前的值还是A,符合预期,那么它也会更新成功,从操作上看并没有什么不对,更新成功也是对的,但是这样是有隐患的,这个网上有好多关于ABA问题隐患的解读,为了解决这个问题,java引入了版本的概念,相当于上述操作变为了A1----B2----A3,这样就非常明确了,这个版本相信大家也猜到那就是valueOffset,所以在AtomicInteger中进行cas操作时除了this、expect、update之外还有一个valueOffset的参数进行版本的区分,就是为了解决ABA问题的

自旋:自旋是CAS的一个操作周期
如果一个线程特别倒霉,每次获取的值都被其他线程的修改了,那么它就会一直进行自旋比较,直到成功为止,在这个过程中cpu的开销十分的大,所以要尽量避免。


 

posted @ 2021-01-07 22:54  发条良子  阅读(233)  评论(0)    收藏  举报