前言
从JDK1.6开始,JVM对锁进行了各种优化,目的就是为了在线程间更高效的共享数据和解决互斥同步的问题。从锁优化的话题开始,可以引申出很多考点面试题,比如锁优化的技术、各优化技术的细节、CAS实现原理、CAS的ABA问题及如何解决等,持续发散还会引发更多问题,例如逃逸分析等,可以看出技术点都是相关联的,需要不断积累和梳理。
面试官:JVM实现了哪些锁优化技术?
小白:自旋锁、自适应自旋锁、锁粗化、锁消除、偏向锁、轻量级锁。
面试官:介绍一下自旋锁?为什么引入自旋锁?
小白:自旋锁就是在请求获取锁,又不能马上获取到时,让当前线程在不放弃处理器执行时间的情况下执行忙循环,尝试等待锁被释放,再获取锁。引入自旋锁是为了节省线程挂起和恢复的开销。
面试官:你刚刚说引入自旋锁节省了线程挂起和恢复的开销,但循环也是需要占用处理器时间的,那这个自旋的次数如何控制?
小白:默认是10次,也可以通过JVM参数-XX:PreBlockSpin配置,当然这些自旋都是固定的,所以引入了自适应自旋锁,自旋的次数由前一次在同一个锁上的自旋次数和锁的拥有者的状态来决定。如果前面线程成功获取锁并且正常运行,那么本次获取锁的可能性很大,所以自旋的次数相对多一些;如果前面线程很少成功获取锁,那么本次获取锁的概率也很小,就可能不执行自旋了。
面试官:锁粗化优化了什么?
小白:如果在一段代码中同一线程反复获取、释放同一个对象的锁,将会生产不必要的性能开销,所以需要把获锁的范围扩大,对同一个对象的锁操作只进行一次,在头部获取锁,在尾部释放锁。
面试官:锁消除是什么?
小白:锁消除是指JIT在运行时分析到使用了锁的同步代码在实际运行时不可能存在共享数据被竞争的情况,对锁进行去除。例如如果一个局部变量在方法内部不可能被外部引用,那么它就不需要加锁控制,可以去掉锁。_(注意:_如果你的回答中提到了逃逸分析,面试官很有可能会问你什么是逃逸分析,提前做好准备)
面试官:详细说一下偏向锁?
小白:偏向锁就是如果线程持有了锁,在后续的过程中,只要该锁没有被其它线程持有,那么持有偏向锁的线程将不再需要进行同步操作。这个偏向锁的相关信息是保存在Java对象的对象头中的。在HotSpot虚拟机中,Java对象在内存中存储的布局分为3块区域:对象头、实例数据和对齐填充。对象头包含两部分,第一部分包含对象的HashCode、分代年龄、锁标志位、线程持有的锁、偏向线程ID等数据,这部分数据的长度在32位和64位虚拟机中分别为32bit和64bit,官方称为Mark World,用一张图展示不同状态下,对象头中存储的内容。
一个普通Java对象刚开始是处于无锁状态的,Mark World中存储的数据如上图中的第一行所示。当虚拟机启动了偏向锁,锁对象第一次被线程获取的时候,锁标识位置为01,同时使用CAS将获取到这个锁的线程ID设置到Mark World中,如果CAS操作成功,那么这个线程将可以继续执行相关的同步代码。如果此时有其它线程尝试获取锁,有两种情况,一种是锁对象未被锁定,则偏向锁被撤销,恢复到无锁状态;另一种是对象被锁定,那么偏向锁失效,同时升级为轻量级锁,会在当前线程的栈帧中创建一个锁记录的空间,这个空间存储对象头中Mark World的拷贝,就是复制一份到这个锁记录空间,同时虚拟机使用CAS尝试将这个锁记录空间的指针更新到Mark World,如果CAS操作成功,那么当前线程获取到锁,此时锁状态处于轻量级锁,锁标志位置为00。
面试官:你刚刚说到虚拟机使用CAS进行更新操作,Java中的CAS是什么及如何实现的?
小白:CAS(Compare and swap)是比较和替换,是一种通过硬件实现并发安全的常用技术,底层通过利用CPU的CAS指令对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。它的实现过程是,有3个操作数,内存值V,旧的预期值E,要修改的新值U,当且仅当预期值E和内存值V相同时,才将内存值V修改为U,否则什么都不做。CAS底层实现使用了C++,在其代码中会根据操作系统和处理器的不同来选择对应的调用代码,以Windows和x86处理器为例,如果是多处理器,通过带lock前缀的cmpxchg指令对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作;如果是单处理器,通过cmpxchg指令完成原子操作。
面试官:那你知道CAS中的ABA问题吗?
小白:知道,CAS是当且仅当旧的预期值E和内存值V相同时,才将内存值V修改为U,也就是如果内存值V没有发生变化则更新,但是有可能发生内存值原来是A,中间被改成B,后来又被改成A,此时再使用CAS进行检查时发现没有变化,但是实际上发生了变化,这就是ABA问题。
面试官:这个问题如何解决?
小白:Java并发包下的AtomicStampedReference可以解决ABA问题,内部实现上添加了一个类似于版本号作用的stamp属性,它是被自动更新的。实现上首先检查当前引用是否等于预期引用、当前stamp是否等于预期stamp,如果全部相等,则以原子方式将该引用和该stamp的值设置为给定的更新值。
最后
其实不同成长阶段会有不同的技术能力要求,就像JVM,只有达到一定的职业高度,才会有它很重要的认知,只有弄清楚虚拟机底层原理,才能走向更高的境界。所以掌握JVM是逐渐成长为高级Java工程师的过程中不可或缺的一部分,想要升职涨薪、跳槽大厂面试加分,就必须弄清JVM的原理与调优。
在面试中,常会问一些JVM调优的面试题,其中包括内存泄漏、字符串、垃圾回收等方面。尤其是大厂,十分爱考察求职者对底层执行原理的掌握,看你到底水平有多高,如果没有对JVM进行深入的了解以及实践,那你就有很大几率会在面试中“out”,而且在要薪酬上也比较吃亏。
那么如何才能快速掌握JVM的原理与应用呢?又该怎样在面试环节拿下JVM相关的面试题,让面试官刮目相看?
学习JVM,可以让你更深入理解Java语言,还可以让你学会用基础的知识解决难题,提高工作效率。
学习完JVM可以落地的知识:
- 根据自己的业务场景选择适合自己的GC实现
- 观察gc频率优化内存的分配策略
- 根据dump日志来分析程序瓶颈位置或找出崩溃原因
- 根据JIT编译优化流程、代码
此外,还能让你的代码在写法上,更安全、更高效,避免内存溢出的问题。随着对JVM的掌握越来越多,你的收入水平以及技术能力都逐渐向高级开发工程师迈进。
适合这样的你
如果你:
- 很想掌握JVM,学会解决JVM内存溢出、内存泄露、JVM调优等问题
- 想对Java有更深入的理解,提升Java性能
- 希望面试时能够从容应对JVM的相关问题
- 想要突破25k月薪的门槛
- 正在向高级开发工程师发展进阶
一、JVM内存区域划分
二、JVM执行子系统
三.垃圾回收器和内存分配策略
四、编写高效优雅Java程序
五、性能优化
JVM面试题总结
程序员必备书单
《Java核心知识点合集(283页)》
内容涵盖:Java基础、JVM、高并发、多线程、分布式、设计模式、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat、数据库、云计算等
《Java中高级核心知识点合集(524页)》
《Java高级架构知识点整理》
《1000道 互联网Java工程师面试题 (485页)》
Java各知识点综合面试专题(1000+题)
这套题库里面中包含了以下很多个模块(都有单独的PDF文档):并发编程,多线程,集合框架,设计模式,数据库,性能优化,RabbitMQ消息中间件,ActiveMQ消息中间件,Dubbo,JVM,Kafka,MongoDB,MyBatis,MySQL,Netty,Nginx,Redis,Tomcat,Zookeeper,Spring,SpringBoot,SpringCloud,SpringMVC,…….
因为文章内容实在是太多了,不能够给大家一一体现出来,每个章节都有更加细化的内容。大家需要完整版文档的小伙伴,可以一键三连,下方获取免费领取方式!