一、对象的创建过程
对象的创建过程大致可以分为六步,其中对象的分配尤为重要:
二、对象分配内存
一般来说对象分配内存有两种方式:
第一种是指针碰撞,这是一种比较理想的方式:如果Java堆是绝对规整的:一边是用过的内存,一边是空闲的内存,中间一个指针作为边界指示器;分配内存只需向空闲那边移动指针,这种分配方式称为"指针碰撞"(Bump the Pointer);
第二中方式是空闲列表,这中方式没有指针碰撞那么理想,但是也有他自身的优势:如果Java堆不是规整的:用过的和空闲的内存相互交错; 需要维护一个列表,记录哪些内存可用;分配内存时查表找到一个足够大的内存,并更新列表,这种分配方式称为"空闲列表"(Free List);
这两种方式各有各的优势,具体选用方式,需要根据垃圾收集器来进行选择,主要是根据垃圾回收器有无压缩来选择。
三、线程安全问题
在进行并发操作时,上面两种方式分配内存的操作都不是线程安全的,所以考虑到解决这种线程安全问题:
第一种:加锁,这种方式是开发过程中最容易使用的方式,也是最简单的方式,但是这种方式的缺点就是执行效率太低。
第二种:本地线程分配缓冲区(TLAB):把分配内存的动作按照线程划分在不同的空间中进行:在每个线程在Java堆预先分配一小块内存,称为本地线程分配缓冲区(Thread Local Allocation Buffer,TLAB);哪个线程需要分配内存就从哪个线程的TLAB上分配。
四、对象的结构
对象的结构分为三部分:
第一部分:Header(对象头),对象头又有两给部块组成
1.自身运行时数据(Mark word),这部分数据是对象主要存储的数据,主要有:
a.哈希值:这个值是通过一个native方法获取
b.GC分代年龄:这个是垃圾回收算法使用
以上介绍两种作用是太相同的,至于剩下的:c.锁状态标志、d.线程持有的锁、e.偏向线程ID、f.偏向时间戳都是作用于线程优化的
2.类型指针:对象指向类对象本身数据的指针
第二部分:实例数据(InstanceData):
实例数据部分是对象真正存储的有效信息,也既是我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的都需要记录下来。 这部分的存储顺序会受到虚拟机分配策略参数(FieldsAllocationStyle)和字段在Java源码中定义顺序的影响。HotSpot虚拟机 默认的分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),从分配策略中可以看出,相同宽度的字段总是被分配到一起。在满足这个前提条件的情况下,在父类中定义的变量会出现在子类之前。如果 CompactFields参数值为true(默认为true),那子类之中较窄的变量也可能会插入到父类变量的空隙之中。
第三部分:对齐填充(Padding)
第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。对象头正好是8字节的倍数(1倍或者2倍),因此当对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。
五、对象的访问定位
对立对象是为了使用对象.Java程序需要通过在栈上的reference数据操作堆上的具体对象. 由于reference类型在Java虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位,访问堆中的对象的具体位置,所以对象的访问方式取决于虚拟机的实现而定的.。简单来说 栈指向堆中只会指向地址,至于地址存储的是否对象,我们是无法知道的,
解决这一问题目前有两种方式:
第一种:句柄指针:栈指向堆中的句柄池,句柄池中有实例对象的地址,通过句柄池指向对象
优点:栈地址永远不会改变,或者说引用地址不会改变
第二张:直接指针:reference中存储直接对象的地址,但是必须考虑放置访问类型数据的相关信息
优点:效率快,节省了一次指针定位的时间开销