面试题:JVM 对锁都进行了哪些优化?

文章目录

  • 锁优化
  • 自旋锁和自适应自旋
  • 锁消除
  • 锁粗化
  • 逃逸分析
  • 方法逃逸
  • 线程逃逸
  • 通过逃逸分析,编译器对代码的优化


锁优化

jvm 在加锁的过程中,会采用自旋、自适应、锁消除、锁粗化等优化手段来提升代码执行效率。

自旋锁和自适应自旋

现在大多的处理器都是多核处理器 ,如果在多核心处理器,有让两个或者以上的线程并行执行,我们可以让一个等待线程不放弃处理器的执行时间。设置一个等待超时时间,看线程是否能够很快的释放锁,在等等待的这段时间可以执行一个空循环,让当前线程继续占用 CPU 的时间片。这就是所谓的「自旋锁」。

JVM 中可以通过 +XX:UseSpinning来开启自旋锁,在 JDK1.6 过后默认为我们开启。由于自旋锁的使用会让锁的竞争者占用更多的处理器时间, JVM 规定了一个自旋次数的一个参数。我们可以通过 -XX:PreBlockSping来进行更改(默认10次)。

偏向锁、轻量级锁的状态转化及对象 Mark Word 的关系转换入下图所示:

图片

锁消除

锁消除是指虚拟机即时编译器在运行时检测到某段需要同步的代码不可能存在共享数据竞争而实施的一种对锁进行消除的优化策略。锁消除的主要判断依据于逃逸分析。如果判断一段代码,在堆上所有的数据都不会逃逸出去被别的线程访问到,那就把它当作栈上的数据对待,认为它们是私有的,同步加锁就无需进行。

下面是三个字符串 x, y, z 相加的例子,无论是从源代码上还是逻辑上都没有进行同步

public String concatStr(String x, String y, String z) {return  x + y + z;
}

String 是一个不可变的类,对字符的链接总是生成新的 String 对象来进行的,因此 Javac 编译器会对 String 链接进行自动优化,在 jdk5 之前字符串链接会转换为 StringBuffer;在 jdk5 之后会转换为 StringBuilder 对象连续的 append()操作,我们看看 javac 过后,反编译的结果:

public String concatStr(String x, String y, String z) {StringBuilder sb = new StringBuilder();sb.append(x);sb.append(y);sb.append(z);return  sb.toString();
}

我们再来看看 javap反编译的结果:

图片

javap反编译的结果

这里大家可能会担心 StringBuilder不是线程安全的的操作会存在线程安全的问题吗?这里的答案是不会,x + y + z操作的优化「经过逃逸分析」过后,他的动态作用域被限制在了 concatStr方法内,就是说当前实际执行的 StringBuilder 的操作在 concatStr 方法内部,「其他的外部线程无法访问」到,所以这里「虽然有锁,但是可以被安全的消除掉。所以当我们进行编译过后,这段代码就会忽略掉所有的同步措施直接执行。」

锁粗化

原则上,我们在写代码的时候,总是推荐将同步块的作用范围限制得尽可能的小–只在共享数据的实际操作作用域中才进行同步,这样也是为了使得需要同步的操作尽可能的变少,即使存在锁的竞争,等待的锁的线程也能很快的获取到锁。大多数情况下,上面的原则都是正确的,但是如果「一系列的连续操作都是对同一个对象反复加锁和解锁,甚至加锁操作时出现在循环体之中」的,那即使没有线程的竞争,频繁的进行相互操作也会导致不必需要的性能损耗

StringBuffer buffer = new StringBuffer();
/**  锁粗化 */
public void append(){buffer.append("aaa").append(" bbb").append(" ccc");
}

上面的代码每次调用 buffer.append 方法都需要加锁和解锁,如果 JVM 家册到有一串连续的对同一个对象加锁和解锁的操作,就会将其合并成一次范围更大的加锁解锁操作,即在第一个 append 方法执行的时候进行加锁,最后一个 append 方法结束后进行解锁。

逃逸分析

逃逸分析(Escape Analysis),是一种可能减少有效 Java 程序中同步负载和内存堆分配压力的跨全局函数数据流分析算法。通过逃逸分析, Java Hotspot 编译器能够分析出一个新的对象引用范围从而决定是否要将这个对象分配到堆上,「逃逸分析的基本行为就是分析对象的动态作用域。」

方法逃逸

当一个对象在方法里面被定义后,它可能被外部方法所引用,例如调用参数传递到其他方法中,这种称为方法逃逸。

线程逃逸

当一个对象可能被外部线程访问到,比如:赋值给其他线程中访问的实例变量,这种称为线程逃逸。

通过逃逸分析,编译器对代码的优化

如果能够证明一个对象不会逃逸到到方法外或线程外(其他线程方法或者线程无法通过任何方法访问该变量),或者逃逸程度比较低(只逃逸出方法而不逃逸出线程)则可以对这个对象采用不同程度的优化:

1、栈上分配(Stack Allocations)完全不会逃逸的局部变量和不会逃逸出线程的对象,采用栈上分配,对象就会跟随方法的结束自动销毁。以减少垃圾回收器的压力。

2、标量替换(Scalar Replacement)有个对象可能不需要作为一个连续的存储结果存储也能被访问到,那么对象的部分(或者全部)可以不存储在内存,而是存储在 CPU 寄存器中。

3、同步消除(Synchronization Elimination)如果一个对象发现只能在一个线程访问到,那么这个对象的操作可以考虑不同步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/237856.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

易点易动设备管理系统:解决企业设备管理难题的利器

在现代企业中,设备管理是一个至关重要的环节。无论是制造业、物流业还是服务业,设备的高效管理对于企业的运营和竞争力都至关重要。然而,许多企业在设备管理方面面临着各种挑战。为了解决这些难题,易点易动设备管理系统应运而生。…

C语言数组与指针的关系,使用指针访问数组元素方法

数组与指针 如果您阅读过上一章节“C语言数组返回值”中的内容,那么您是否会产生一个疑问,C语言的函数要返回一个数组,为什么要将函数的返回值类型指定为指针的类型?换句话说,C语言中数组和指针到底是什么关系呢&…

[vue]Echart使用手册

[vue]Echart使用手册 使用环境Echart的使用Echart所有组件和图表类型Echart 使用方法 使用环境 之前是在JQuery阶段使用Echart,直接引入Echart的js文件即可,现在是在vue中使用,不仅仅时echarts包,还需要安装vue-echarts: "…

Pycharm解释器的配置: System Intgerpreter 、Pipenv Environment、Virtualenv Environment

文章目录 前提1. 环境准备2. 了解虚拟环境 一、进入Interpreter设置页二、添加Interpreter1. 方式一2. 方式二 三、 System Interpreter四、 Pipenv Environment前提条件:详细步骤1) 选择pipenv2) 设置Base Interpreter3) 设置Pip…

程序员福利:好用的第三方api接口

空号检测:通过手机号码查询其在网活跃度,返回包括空号、停机等状态。手机在网状态:支持传入三大运营商的号码,查询手机号在网状态,返回在网等多种状态。反欺诈(羊毛盾):反机器欺诈&a…

计算机网络个人小结

不同层的数据报的名称 应用层: data TCP层: segment IP 层: packet MAC层: frame MTU vs MSS: MTU:一个网络包的最大长度,以太网中一般为 1500 字节。 https://www.xiaolincoding.com/network/1_base/how_os_deal_network_package.html#linux-%E7%BD%91…

Centos9(Stream)配置Let‘s Encrypt (免费https证书)

1. 安装snap,用来安装certbot: sudo dnf install epel-release sudo dnf upgrade sudo yum install snapd sudo systemctl enable --now snapd.socket sudo ln -s /var/lib/snapd/snap /snap snap install core snap refresh core 2. 安装 certbot命令…

Ubuntu搭建Nodejs服务器

转自:https://www.8kiz.cn/archives/3228.html 在Ubuntu上搭建Node.js服务器,按照以下步骤进行: 打开终端。 使用包管理器安装Node.js。可以使用以下命令安装Node.js: sudo apt update sudo apt install nodejs安装Node.js后&a…

基于Java SSM框架实现人事员工考勤签到请假管理系统项目【项目源码+论文说明】

基于java的SSM框架实现人事员工考勤签到请假管理系统演示 摘要 在高速发展的时代,众多的软件被开发出来,给用户带来了很大的选择余地,而且人们越来越追求更个性的需求。在这种时代背景下,人们对人事管理系统越来越重视&#xff0…

css图片属性,图片自适应

CSS 图片属性指南:background-size 和 object-fit 在前端开发中,使用图片是非常常见的。为了让图片在网页中显示得更好,CSS 提供了多种属性来调整和控制图片的大小和布局。其中,background-size 和 object-fit 是两个常用的属性&a…

制作系统安装盘教程——烧录Windows原版镜像

前言 本次教程不经过WinPE工具进行安装Windows原版镜像,而是直接把系统镜像文件直接烧录进U盘,这样做的好处是不经过WinPE安装Win系统的过程,避免有些带木马病毒的WinPE在安装系统的过程把木马病毒带进系统,从而导致文件泄漏。 开…

【JavaWeb学习笔记】13 - JSP浏览器渲染技术

JSP 一、JSP引入 1.JSP现状 1.目前主流的技术是前后端分离(比如: Spring Boot Vue/React),我们会讲的.[看一下] 2. JSP技术使用在逐渐减少,但使用少和没有使用是两个意思,一些老项目和中小公司还在使用JSP,工作期间,你很有可能遇到JSP …

【设计模式-2.5】创建型——建造者模式

说明:本文介绍设计模式中,创建型设计模式中的最后一个,建造者模式; 入学报道 创建型模式,关注于对象的创建,建造者模式也不例外。假设现在有一个场景,高校开学,学生、教师、职工都…

Spring 5.x较上一版本的主要特性

Spring Framework 5.0 是在 2017 年发布的一个主要版本,它带来了许多改进和新特性,相比于 Spring Framework 4.x,主要的升级包括: 基于 Java 8 的基线: Spring 5 需要 Java 8 或更高版本,这使得框架得以利用…

LLaMA-Factory如何对Tokenization步骤提速

问题 数据量稍微大一些,在运行这个步骤“Running tokenizer on dataset”就要等半小时以上,然后才知道后续是否会报错。Tokenization步骤看上去并没有使用到GPU,只是在CPU上运行的。 是否有什么方法对这个步骤进行加速呢,比如多C…

c/c++实现隐藏密码

1. 我们在平常输入密码是,不能直接显示密码,应该显示*或者其它字符。 void inputPwd(char *pwd,int length) {char ch; // 用来存储getch()都进来的字符int i 0;while (1){ch getch(); /*这个函数可以实现输入不回显(输入不在终端显示)*/if (ch \r) /*密码输入结束输入回车…

Lua脚本在Redis中的高效应用

大家好,我是升仔 引言 Redis作为一个多功能的键值数据库,其性能非常出色,特别是在处理高速缓存和消息队列方面。而Lua脚本的引入,则进一步增强了Redis的能力。 1、Lua脚本与Redis的结合 Redis选择Lua作为脚本语言,主…

12.21

一、注意事项 1.CtrlShiftT用于从jar中查找类,这个eclipse键盘快捷键可以帮助快速找到类 2.更新upm和rest遇到重复解决方法 把upm文件里面多出来的三行代码删掉,右击upm文件,点击小组,点击标记为已解决,点击OK&#x…

ElasticSearch DSL Bool查询

Bool 查询的三个主要成分:must、should 和 must_not。 must: 这个条件是必须满足的。比如,你想找一本关于 Python 编程的书,那么你会在 must 里写上关键词 “Python”。 should: 这相当于你的偏好条件,…

OpenVAS 数据库管理

数据库概述为了更好地理解OpenVAS数据库管理,我们首先需要了解数据库的概念。数据库是一个组织和存储数据的软件系统,它允许用户在其中存储、访问和更新数据。在计算机系统中,数据库通常是用来存储大量数据的一个集合,这些数据可以…