Java 对象头、监视器、synchronized

 

对象头

HotSpot虚拟机的对象头包括两部分信息:

  1. MarkWord
    第一部分MarkWord,用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit,官方称它为“MarkWord”。
  2. Class类型指针
    对象头的另外一部分是Class类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例.
  3. 数组长度(只有数组对象有) 
    如果对象是一个数组, 那在对象头中还必须有一块数据用于记录数组长度.

实例数据

实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。

对齐填充

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

对象头大小计算

要点 
1. 在32位系统下,MarkWord是4字节,Class类型指针是4字节,所以对象头为8字节。 
2. 在64位系统下,不开启压缩指针时,MarkWord是8字节,Class类型指针是8字节,所以对象头为16字节。 
3. 64位开启指针压缩的情况下(压缩Class类型指针),MarkWord是8字节,Class类型指针是4字节,所以对象头为12字节。 数组长度4字节+数组对象头8字节(数组markword为4字节(64位未开启指针压缩的为8字节))+对象引用4字节(未开启指针压缩的64位为8字节)+对齐4字节=16字节。 
4. 静态属性不算在对象大小内。

64位默认开启压缩指针默认开启,关闭采用JVM参数:-XX:UseCompressedOops。

查看对象头

    <dependency>
      <groupId>org.openjdk.jol</groupId>
      <artifactId>jol-core</artifactId>
      <version>0.10</version>
    </dependency>
import org.openjdk.jol.info.ClassLayout;

/**
 * @Author: ZhangHao
 * @Description: 对象头查看测试
 * @Date: 2020/9/10 14:29
 * @Version: 1.0
 */
public class Test {
    public static void main(String[] args) {
        System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
    }
}

结果:

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

结果中低位字节在前(小端显示方式)。00000001的01即对应锁标志位,01表示无锁。01前的0即是对应否偏向锁位,0表示非偏向锁。

监视器(Monitor)

(1)Monitor:

1.Monitor是一种用来实现同步的工具。

2.Monitor与每个java对象相关联,即每个java对象都有一个Monitor与之对应。

3.Monitor是实现Sychronized的基础。

(2)Monitor的基本结构:

1.Owner字段:初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL。

2.EntryQ字段:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程。

3.RcThis字段:表示blocked或waiting在该monitor record上的所有线程的个数。

4.Nest字段:用来实现重入锁的计数。

5.HashCode字段:保存从对象头拷贝过来的HashCode值(可能还包含GC age)。

6.Candidate字段:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降;Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁。

(3)Monitor与java对象以及线程如何关联:

可以参考:java对象堆内存结构

1.如果一个java对象被某个线程锁住,则该java对象的Mark Word字段中LockWord指向monitor的起始地址。

2.Monitor的Owner字段会存放拥有相关联对象锁的线程id。

(4)Monitor实现:

1.Monitor是在jvm底层实现的,底层代码是c++。

2.Monitor的enter方法:获取锁。

3.Monitor的exit方法:释放锁。

4.Monitor的wait方法:为java的Object的wait方法提供支持。

5.Monitor的notify方法:为java的Object的notify方法提供支持。

6.Monitor的notifyAll方法:为java的Object的notifyAll方法提供支持。

synchronized

synchronized 是 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码,而这段代码也被称为临界区。

  synchronized 有多个叫法,而每个叫法都表明synchronized 的特性:

1、内置锁(也称隐式锁,监视器锁):synchronized 是内置于JDK中的,底层实现是native;同时,加锁、解锁都是JDK自动完成,不需要用户显式地控制,非常方便。

2、同步锁:synchronized 用于同步线程,使线程互斥地访问某段代码块、方法。这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,知道线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。

3、对象锁:准确来说,是分为对象锁、类锁。synchronized 以当前的某个对象为锁,线程必须通过互斥竞争拿到这把对象锁,从而才能访问 临界区的代码,访问结束后,就会释放锁,下一个线程才有可能获取锁来访问临界区(被锁住的代码区域)。synchronized锁 根据锁的范围分为 对象锁 和 类锁。对象锁,是以对象实例为锁,当多个线程共享访问这个对象实例下的临界区,只需要竞争这个对象实例便可,不同对象实例下的临界区是不用互斥访问;而类锁,则是以类的class对象为锁,这个锁下的临界区,所有线程都必须互斥访问,尽管是使用了不同的对象实例;

猜你喜欢

转载自blog.csdn.net/haoranhaoshi/article/details/108501633