JVM中对象分为三部分
对象头:
Mark Word(标记字段):默认存储对象的HashCode,分代年龄和锁标志位信息。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。
Klass Point(类型指针):对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
**实例变量:**这部分主要是存放类的数据信息,父类的信息。
填充数据:于虚拟机要求对象起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐。
Syschronied的作用
sychronied关键字是用来控制线程同步的,在多线程的环境下,synchronized关键字修饰的代码块是不能同时执行的, 可以用来修饰类 方法 代码块 和变量,可以保证原子性,可见性,保证代码的重排序。Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:普通同步方法,锁是当前实例对象;静态同步方法,锁是当前类的class对象;同步方法块,锁是括号里面的对象。
在java6 之前 synchronized是属于重量级锁的,效率低下,因为监视器(monitor)是依赖于底层的操作系统实现的,java线程是映射到操作系统的原生线程上的,如果要挂起或者唤醒一个线程,需要操作系统来帮忙,操作系统要实现线程之间的切换时就需要从用户态切换成内核态,这个切换是很费时间的,在java6 之后java从jvm层面对sychronied锁进行了优化,引用了大量的优化,比如:锁粗化,锁消除,偏向锁,轻量级锁等技术来减少锁的开销。
锁粗化:
锁消除:
偏向锁:
轻量级锁:
Syschronied在底层的实现原理
每个java对象都有一个关联的monitor,使用Synchronize的时候,jvm会根据使用的环境找到monitor,根据monitor的状态进行加锁和解锁的判断。如果加锁成功就会成为这个monitor的唯一使用者,在monitor释放前都不能被其他线程获取。同步代码快使用 monitorenter 和 monitorexit 这两个字节码指令获取和释放 monitor。 当执行monitorenter的指令时候,当前线程将试图获取对象锁所对应的moitor所有权,如果对象锁的monitor计数器为0的时候,当前线程就可以获取monitor,并且把monitor的计数器设置为1,并且成功的获取到了对象锁。如果当前对象已经获取到了这个monitor的所有权,那么他也可以选择重入这个monitor,重入的时候monitor的计数器会加1,如果其他线程已经获取了monitor的所有权,那么这个线程将会被阻塞,直到其他线程执行完毕,即monitorexit指令执行,计数器会设置为0;
为啥会有两个monitor呢
主要是为了保证线程在异常的情况下退出的时候,锁没有得到释放,就会造成死锁。所以最后一个monitor是为了保证在异常的情况下,锁可以得到释放,避免死锁。