Java对象在内存中存储结构和synchronized原理

对象在内存中存储的结构

对象在内存中存储的结构由三部分组成:对象头、实例数据、对齐填充。
转载

对象头

根据类型可分为两种

  • 普通对象包含:Mark Word、元数据指针(Klass Pointer)
  • 数组对象包含:Mark Word、元数据指针(Klass Pointer)、Array Length

MarkWord(标记字段):存储代表该对象运行时的一些信息,哈希码、GC分代年龄、锁标志位、偏向线程ID、偏向时间戳等信息。

Class Word (类型指针信息): 代表类型信息, 该部分是一个指针, 指向方法区(类的元数据空间) 中的实际类型,虚拟机通过这个指针来确定这个对象是哪个类的实例。

Array Length (数组长度) : 如果是数组,对象头中还有一块用于存放数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。

在32位虚拟机中, 对象头占64位, 其中Mark Word和Class Word各占32位. 而在64位虚拟机中, 不开启指针压缩的情况下对象头占128位, Mark Word和Class Word各占64位. 开启指针压缩之后, Class Word占32位, 对象头减少到占128位.

实例数据

实例数据部分是对象真正存储的有效信息,也就是我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的都需要记录下来。 这部分的存储顺序会受到虚拟机分配策略参数(FieldsAllocationStyle)和字段在Java源码中定义顺序的影响。
HotSpot虚拟机 默认的分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),从分配策略中可以看出,相同宽度的字段总是被分配到一起。在满足这个前提条件的情况下,在父类中定义的变量会出现在子类之前。如果 CompactFields参数值为true(默认为true),那子类之中较窄的变量也可能会插入到父类变量的空隙之中。

对齐填充

第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。对象头正好是8字节的倍数(1倍或者2倍),因此当对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。

synchronized的锁的原理

实现synchronized两个重要的概念:一个是对象头,另一个是monitor
一般而言,synchronized使用的锁对象是存储在Java对象头里。它是轻量级锁和偏向锁的关键。
Monitor是一个同步工具,它内置于每一个Object对象中,相当于一个许可证。拿到许可证即可以进行操作,没有拿到则需要阻塞等待。
在hotspot虚拟机中,通过ObjectMonitor类来实现monitor。
monitor对象存在于每一个java对象的对象头(存储指针的指向),synchronized锁便是通过这种方式获取的,也是为什么java中任何对象都可以作为锁的原因,同时也是 notify/notifyAll/wait 方法等存在于顶级对象Object中的原因。

锁升级

偏向锁升级轻量级锁:当一个对象持有偏向锁,一旦第二个线程访问这个对象,如果产生竞争,偏向锁升级为轻量级锁。

轻量级锁升级重量级锁:一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。
但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

wait和notify的原理:

调用wait方法,首先会获取监视器锁,获得成功以后,会让当前线程进入等待状态进入等待队列并且释放锁。

当其他线程调用notify后,会选择从等待队列中唤醒任意一个线程,而执行完notify方法以后,并不会立马唤醒线程,
原因是当前的线程仍然持有这把锁,处于等待状态的线程无法获得锁。必须要等到当前的线程执行完按monitorexit指令以后,也就是锁被释放以后,处于等待队列中的线程就可以开始竞争锁了。

wait和notify为什么需要在synchronized里面?

wait方法的语义有两个,一个是释放当前的对象锁、另一个是使得当前线程进入阻塞队列,而这些操作都和监视器是相关的,所以wait必须要获得一个监视器锁。

而对于notify来说也是一样,它是唤醒一个线程,既然要去唤醒,首先得知道它在哪里,所以就必须要找到这个对象获取到这个对象的锁,然后到这个对象的等待队列中去唤醒一个线程。

Hotspot JVM中,32位机器下,Integer对象的大小是int的几倍?

int占用的是4字节(32位机器跟64位一样,由编译器决定)。由以上分析可得Integer的结构如下:
转载

Integer只有一个int类型的成员变量value,所以其对象实际数据部分的大小是4个字节,然后再在后面填充4个字节达到8字节的对齐,所以可以得出Integer对象的大小是16个字节。

因此,我们可以得出Integer对象的大小是原生的int类型的4倍。
关于对象的内存结构,需要注意数组的内存结构和普通对象的内存结构稍微不同,因为数据有一个长度length字段,所以在对象头后面还多了一个int类型的length字段,占4个字节,接下来才是数组中的数据,如下:
转载
参考链接:https://www.jianshu.com/p/0a63a4d22765
参考链接:https://www.cnblogs.com/chenxiaoxian/p/10426475.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值