网站建设和开发,杭州老牌的网站建设,vs2013怎么做网站,网上销售一.Java对象内存布局和对象头
在HotSpot虚拟机里#xff0c;对象在堆内存中的存储布局可以划分为三个部分#xff1a;对象头(Header) 、实例数据 (Instance Data) 和 对文填充 (Padding)。 对象内部结构分为#xff1a;对象头、实例数据、对齐填充#xff08;保证8个字节的…一.Java对象内存布局和对象头
在HotSpot虚拟机里对象在堆内存中的存储布局可以划分为三个部分对象头(Header) 、实例数据 (Instance Data) 和 对文填充 (Padding)。 对象内部结构分为对象头、实例数据、对齐填充保证8个字节的倍数。对象头分为对象标记markOop和类元信息klassOop类元信息存储的是指向该对象类元数据klass的首地址。
对象头
在64位系统中Mark Word占了8个字节类型指针占了4个字节采用了压缩指针压缩前是8个。
对象标记Mark Word 类元信息(又叫类型指针)
指向它的类元数据方法区的Klass对象的指针虚拟机通过这个指针来确定这个对象是哪个类的实例。
实例数据
存放类的属性(Field)数据信息包括父类的属性信息。
对齐填充
虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的仅仅是为了字节对齐 这部分内存按8字节补充对齐。
二.Synchronized与锁升级
2.1为什么 JDK1.6 以前synchronized是重量级锁
什么是上下文操作
线程多不断阻塞、唤醒一次阻塞唤醒操作需要两次上下文切换
就是第一次由用户态切换到内核态由内核将线程阻塞掉将线程数据原本在寄存器或者cache运送到内存或队列中第二次由用户态切换到内核态由内核将线程唤醒将线程数据从内存或者队列送到寄存器或cache中。如果频繁进行上下文切换CPU就大多数消耗在内核数据共享中影响了真正工作线程功能
上下文切换过高会导致CPU像个搬运工频繁在寄存器和运行队列之间奔波 更多的时间花在了线程切换而不是真正工作的线程上。直接的消耗包括CPU寄存器需要保存和加载系统调度器的代码需要执行。间接消耗在于多核cache之间的共享数据。
因为监视器锁monitor依赖底层操作系统的原语 pthread_mutex_lock 实现当多个线程同时调用这个函数的时候会让每一个线程都切换到内核态由内核协调哪个线程获取到锁哪些线程无法获取到锁。获取锁成功的线程会被唤醒获取锁失败的线程会被内核进行阻塞。所以它是一个重量级操作。操作系统实现线程之间的切换需要从用户态到内核态的转换这种状态切换需要耗费处理器时间。
总之一句话重量级锁是需要依靠操作系统来实现互斥锁的这导致大量上下文切换消耗大量CPU影响性能。
Java SE 1.6开始为了减少获得锁和释放锁所带来的性能消耗引入了轻量级锁和偏向锁。在Java SE 1.6中锁一共有4种状态级别从低到高依次是无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级意味着偏向锁升级成轻量级锁后不能降级为偏向锁。这种锁升级却不能降级的策略目的是为了提高获得锁和释放锁的效率。
2.2偏向锁
Hotspot 的作者经过研究发现大多数情况下锁不仅不存在多线程竞争还存在锁由同一线程多次获得的情况为了让线程获得锁的代价更低而引入了偏向锁这个线程就是锁的偏向线程它的出现是为了解决只有在一个线程执行同步时提高性能。 首先获取锁对象的 Markword判断是否处于可偏向状态。biased_lock1、且 ThreadId 为空如果是可偏向状态则通过 CAS 操作把当前线程的 ID 写入到 MarkWord 如果 cas 成功那么 markword 就会变成这样。 表示已经获得了锁对象的偏向锁接着执行同步代码块如果 cas 失败说明有其他线程已经获得了偏向锁这种情况说明当前锁存在竞争需要撤销已获得偏向锁的线程并且把它持有的锁升级为轻量级锁这个操作需要等到全局安全点也就是没有线程在执行字节码才能执行如果是已偏向状态需要检查 markword 中存储的 ThreadID 是否等于当前线程的 ThreadID 如果相等不需要再次获得锁可直接执行同步代码块如果不相等说明当前锁偏向于其他线程需要撤销偏向锁并升级到轻量级锁
偏向锁撤销
偏向锁使用一种等到竞争出现才释放锁的机制只有当其他线程竞争锁时持有偏向锁的原来线程才会被撤销。撤销需要等待全局安全点(该时间点上没有字节码正在执行)同时检查持有偏向锁的线程是否还在执行
第一个线程正在执行synchronized方法(处于同步块)它还没有执行完其它线程来抢夺该偏向锁会被取消掉并出现锁升级。此时轻量级锁由原持有偏向锁的线程持有继续执行其同步代码而正在竞争的线程会进入自旋等待获得该轻量级锁。第一个线程执行完成synchronized方法(退出同步块)则将对象头设置成无锁状态并撤销偏向锁重新偏向 。
关闭偏向锁
偏向锁在 Java 6 和 Java 7 里是默认启用的但是它在应用程序启动几秒钟之后才激活如有必要可以使用JVM参数来关闭延迟-XX:BiasedLockingStartupDelay0
如果你确定应用程序里所有的锁通常情况下处于竞争状态那么偏向锁就是多余的此时使用偏向锁是得不到提升的反而可能降低系统性能可以通过JVM参数关闭偏向锁-XX:-UseBiasedLocking
从 JDK 15 开始偏向锁被官方标记为废弃状态
2.3轻量级锁 轻量级锁加锁
加锁的过程很奇妙线程在执行同步代码块之前JVM会先在当前线程的栈帧中创建用于存储锁记录的空间并将对象头中的Mark Work 复制到锁记录中。接下来线程会尝试使用CAS操作将对象头中的Mark Word替换为指向锁记录的指针。如果成功则获取锁失败则表示被占用自旋重试。
轻量级锁解锁
轻量级锁解锁时会使用原子性的CAS操作将栈帧中的 Mark Word替换回到对象头如果成功则表示没有竞争。失败则表示存在竞争锁膨胀为重量级锁。
经过上面的学习我们知道了JVM底层几种锁的实现下面总结了几种锁的优缺点