导语
  上篇分享中提到了对象头Mark Word 的基本概念之后,接下来就可以深入到虚拟机内部了。在多线程程序中,线程之间的竞争是不可避免的,并且这是一种多线程程序的常态。那么如何高效的处理多线程的竞争,是JVM的一项关键优化点,如果将所有的线程处理都交给操作系统,那么整体的处理效率比较低。所以,JVM在进入操作系统处理之前,首先就需要做好前期的准备工作,这样尽可能的避免真实场景的竞争发生。下面就来看看JVM对于锁的优化等问题。

锁机制在JVM中的实现和优化

偏向锁

  偏向锁是JDK1.6 中提出的一种锁的优化方式。核心思想是,如果程序没有竞争,则取消之前已经取得锁的线程同步操作。也就是说,某个锁被线程获取之后,就会进入偏向模式,当线程再次请求这个锁的时候,不需要在进行相关的同步操作,从而节省了操作的时间,如果在此期间有其他的线程进行锁请求,则退出偏向模式。在JVM中使用-XX:+UseBiasedLocking 可以设置启用偏向锁。

-XX:+UseBiasedLocking

  当锁处于偏向模式的时候,对象头会记录获取锁的线程

[JavaThread* |epoch|age|1|01]

  这样,当该线程再次尝试获得锁时,通过Mark Word的线程信息就可以判断当掐你线程是否持有偏向锁。

public class Biased{public static List<Integer> numberList = new Vector<Integer>();public static void main(String[] args) throws InterruptedException{long begin = System.currentTimeMillis();int count = 0;int startnum = 0;while(count<100000000){numberList.add(startnum);startnum+=2;count++;}long end = System.currentTimeMillis();System.out.println(end-begin);}
}

  上述代码使用一个线程对Vector进行写入操作,由于对Vector的访问内部都用同步锁控制,每次add()操作都会请求numberList对象的锁,使用以下参数执行这段程序:

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 -client -Xmx512m -Xms512m

  程序输出结果如下

394

  上述结果表示程序使用394毫秒完成所有的工作。参数中的-XX:BiasedLockingStartupDelay表示虚拟机在启动之后立即使用偏向锁。如果不设置该参数,虚拟机默认会在启动后的4秒才会启用偏向锁,考虑到程序运行时间较短,故做此设置,尽早启用偏向锁。

  如果禁用偏向锁,则只需要使用如下参数启动程序:

-XX:-UseBiasedLocking -client -Xmx512m -Xms512m

  程序输出结果如下

539

  可以看到,偏向锁在竞争较少的情况下,对系统性能有很大的帮助。

  偏向锁在锁竞争激烈的场合没有太强的优化效果,因为大量的竞争会导致持有锁的线程不停地切换,锁也很难一直保持在偏向模式。此时使用锁偏向不仅得不到性能优化,反而是降低了系统性能。因此,在竞争激烈的场合,可以尝试使用-XX:UseBiasedLocking 参数禁用偏向锁。

轻量级锁

  如果偏向锁失败,JVM会让线程申请轻量级锁。轻量级锁在JVM内部使用了一个被称为BasicObjectLock的对象实现,这个对象内部由一个BasicLock对象和一个持有该锁的Java对象指针组成。BasicObjectLock 对象放置在Java栈的栈帧中。在BasicLock对象内部还维护着displaced_header字段,它用于备份对象头部的Mark Word。
  当一个线程持有一个对象的锁的时候,对象头部Mark Word 如下

[ptr   |00] locked

  末尾两位00,整个Mark Word 为指向BasicLock对象指针。由于BasicObjectLock对象在线程栈中,该指针必然指向持有该锁的线程栈空间。当需要判断某一线程是否持有该对象锁的时候,只需要判断对象头的指针是否在当前线程的栈地址范围内。同时,BasicLock 对象的displaced_header字段备份了原对象的Mark Word 内容,BasicObjectLock 对象的obj 字段则指向该对象。

  在JVM中,轻量级锁的代码实现可读性比较好。首先BasicLock 通过set_displaced_header() 方法备份了原对象的Mark Word。接着,通过CAS操作,尝试将BasicLock的地址复制到对象头的Mark Word。如果复制成功,那么加锁成功,否则加锁失败。如果加锁失败,那么轻量级锁就有可能膨胀为重量级锁。如图所示

锁膨胀

  当轻量级锁失败,虚拟机就会使用重量级锁。在使用重量级锁时,对象的Mark Word如下

[ptr   |10] monitor

  末尾的2个比特位被设置成10。整个Mark Word 表示指向monitor对象的指针。在轻量级锁处理失败之后,JVM会执行如下的操作

lock->set_displaced_header(markOopDesc::unused_mark());
ObjectSynchroizer::inflate(THREAD,obj())->enter(THREAD)

  第一步是废弃前面BasicLock备份的对象头信息。第二步则正式启用重量级锁。启用过程分为两步:首先通过inflate()方法进行锁膨胀,其目的是获得对象的ObjectMonitor;然后使用enter()方法尝试进入该锁。
  在调用enter()方法时,线程很可能会在操作系统层面被挂起,此时线程间切换和调度的成本就会比较高。

自旋锁

  在上面提到,锁膨胀之后,进入ObjectMonitor 的enter()方法,线程很可能会在操作系统层面被挂起,这样线程上下文切换的性能损失就比较大。在锁膨胀之后,JVM会做最后的争取,希望线程可以尽快进入临界区从而避免被操作系统挂起。解决这种问题比较有效的手段就是使用自旋锁。

  自旋锁可以使得线程在没有取得锁的之后不被挂起,去执行一个空循环,也就是所谓的自旋,在若干个空循环后,线程如果可以获得锁,则继续执行。若线程依然不能获得锁,才会被挂起。

  在使用自旋锁之后,线程被挂起的概率相对减小,线程执行的连贯性相对加强。因此,对于那些锁竞争不是很强烈的锁占用时间很短的并发线程,具有很积极的效果,但对锁竞争激烈、单线程占用时间长的并发程序,自旋锁在自旋等待之后,往往依然是没有办法获取到对应的锁,白白的浪费了CPU时间,最终还会导致线程不被挂起,从而消耗了很多的资源。

  JDK1.6中,JVM提供-XX:+UseSpinning参数来开启自旋锁,使用-XX:PreBlockSpin参数来设置自旋锁的等待次数。
  JDK1.7后的版本,自旋锁的参数被取消,JVM不再支持由用户配置自旋锁。自旋锁总是被执行,但是自旋的次数由虚拟机自己来决定。

锁消除

  锁消除是JVM在JIT编译的时候,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过锁的消除,可以节省无意义的锁的请求时间。

  这里需要说明一点,既然没有产生锁,为什么还要加锁?在Java应用程序开发过程中,必然会涉及到一些JDK内置的API,例如StringBuffer、Vector等,一些常用的工具类可能被大量使用,虽然这些工具类本身可能有对应的非线程安全版本,但是再开发过程中,可能在没有多线程的场合下使用了这种工具类。

  在这种情况下工具类内部的同步方法就是不必要的,虚拟机可以在运行时,基于逃逸分析技术,捕获一些不可能存在竞争却有申请锁的代码段,并取消一些不必要的锁。从而提高系统性能

  逃逸分析和锁消除的参数分别是 -XX:+DoEscapeAnalysis和-XX:+EliminateLocks 开启。

垃圾回收算法与实现系列-锁在Java虚拟机中的实现和优化相关推荐

  1. 垃圾回收算法与实现系列-锁在应用层的优化思路

    导语   之前的分享中主要介绍了虚拟机内部的对锁机制的优化与具体实现,在实际的开发过程中,还可以通过在应用层的合理优化,达到保证性能的目的,那么下面就学习介绍一下在应用层中如何进行锁的优化. 文章目录 ...

  2. 垃圾回收算法与实现系列-学习GC之前的准备工作

    导语   在学习垃圾回收算法之前,首先需要了解什么是Heap.什么是Root.什么是Object.什么是Stack.什么是Pointer,这写概念都是什么,为什么要在垃圾回收算法中使用,使用这些东西有 ...

  3. java虚拟机多久触发垃圾回收_每日一问:讲讲 Java 虚拟机的垃圾回收

    昨天我们用比较精简的文字讲了 Java 虚拟机结构,没看过的可以直接从这里查看: 每日一问:你了解 Java 虚拟机结构么? 今天我们必须来看看 Java 虚拟机的垃圾回收算法是怎样的.不过在开始之前 ...

  4. 垃圾回收算法与实现系列-JVM无锁实现

    导语   为了确保多线程场景下数据安全,使用锁机制一直是一种优秀的解决方案,但是再高并发场景下,对锁的竞争可能成为性能瓶颈.为此,有出现了一种新的解决方案,被称为是非阻塞同步的方案.这种实现方式不需要 ...

  5. 垃圾回收算法与实现系列-线程安全与锁简介

    导语   锁是多线程软件开发的必要工具,它的基本作用是保护临界区资源不被多个线程同时访问进而受到破坏.如果由于多线程访问造成数据不一致,那么系统将会得到一个错误的结果.通过锁可以让多个线程排队一个一个 ...

  6. 垃圾回收算法与实现系列-String在虚拟机中的实现

    导语   String 字符串一直作为各种编程语言的核心内容存在.作为动态字符的一种是实现方案,应用很广泛.每一种计算机语言对于这种数据结构都进行了特殊的优化和实现.在Java中,String作为引用 ...

  7. 垃圾回收算法与实现系列-GC 标记-清除算法

    导语   在GC 中最重要的算法就是GC标记-清除算法(Mark-Sweep GC).在很多的场景下都还是在使用这个算法来进行垃圾回收操作.就如如同它的名字一样先标记,然后清除.下面就来看看标记清除算 ...

  8. 垃圾回收算法与实现系列-Java堆内存溢出原因

    导语   内存一直是所有开发人员探索的一片天地,再JVM中,内存往往会被分为几块,了解不同的内存区域对编写出优质的代码有很大的帮助.堆内存作为JVM中比较重要的区域,有很多值得我们探索的地方.下面就来 ...

  9. 垃圾回收算法与实现系列-Java的Class文件详解

    导语   对于JVM来说,Class文件作为虚拟机的一个重要接口.无论使用什么样的语言,进行软件开发,只要能将源码编译为Class文件,并放到正确的路径下,那么这种语言就可以被JVM所执行.可以说Cl ...

最新文章

  1. 获得汉字拼音的首字母
  2. 中国财团收购飞利浦照明业务遭美封杀
  3. Annotation版本的HelloWorld
  4. python的convert_python编程开发之类型转换convert实例分析
  5. python中function函数的用法_Python中Function(函数)和methon(方法)
  6. Python自学之乐-python中break continue exit() pass浅析
  7. csv文件导入后台乱码_win7系统下excel打开csv文件出现乱码怎么修复
  8. 压缩文件的后缀html,压缩文件的扩展名是什么
  9. 自学软件测试需要学到哪些内容?
  10. 阿里云邮件推送使用方法
  11. iOS 开发 -- 使用KeyChain保存用户名、密码并实现自动登录
  12. 【题解】将军令 Luogu P3942 (未完成)
  13. 抖音壁纸小程序,星光壁纸小程序2.0版本,升级版
  14. PageRank背后的数学
  15. Springboot使用@EnableCache缓存
  16. 用海伦公式计算三角形的面积 python_java程序设计1-2之用海伦公式计算三角形的面积...
  17. opencv2.4.13在win10+VS2015下的配置过程
  18. oc照片库图片的选择处理
  19. 一头扎紧mysql_[www.java1234.com]一头扎进Mysql视频教程
  20. 华三服务器hdm时间怎么修改,华三服务器HDM命令设置

热门文章

  1. java url 短链接_推荐几个官方腾讯短链接url接口(含PHP演示代码)
  2. nginx虚拟主机配置和反向代理
  3. 【译】区块链是如何工作的——用JavaScript演示
  4. 用 Anaconda 完美解决 Python2 和 python3 共存问题
  5. Android访问数据库(SQL Server 和 MySQL)
  6. LeetCode OJ - Sort List
  7. asp.net调试技巧
  8. 转载:jQuery 1.3.3 新功能
  9. 为什么统计学家应该关注数据挖掘
  10. 初识PHP变量函数语法