好理解的Java内存虚假共享(False Sharing)性能损耗以及解决方案
虚假共享(False Sharing)也有人翻译为伪共享
参考 https://en.wikipedia.org/wiki/False_sharing
在计算机科学中,虚假共享是一种性能降低的使用模式,它可能出现在具有由高速缓存机制管理的最小资源块大小的分布式一致高速缓存的系统中。当系统参与者将定期尝试访问,将永远不会被另一方改变数据,但这些数据共享与数据的高速缓存块被修改,缓存协议可能迫使一位与会者尽管缺乏逻辑必然性的整个单元重载。高速缓存系统不知道该块内的活动,并迫使第一个参与者承担真正共享资源访问所需的高速缓存系统开销。
到目前为止,该术语最常见的用法是在现代多处理器 CPU高速缓存中,其中存储器被缓存在两个字大小的一些小功率的行中(例如,64个对齐的,连续的字节)。如果两个处理器对可存储在一行中的同一存储器地址区域中的独立数据进行操作,则系统中的高速缓存一致性机制可能会在每次数据写入时强制整个线路穿过总线或互连,从而除了浪费系统带宽之外还会导致内存停顿 。虚假共享是自动同步的缓存协议的固有工件,也可以存在于分布式文件系统或数据库等环境中,但目前的流行仅限于RAM缓存。
示例
struct foo {int x;int y;
};static struct foo f;/* The two following functions are running concurrently: */int sum_a(void)
{int s = 0;for (int i = 0; i < 1000000; ++i)s += f.x;return s;
}void inc_b(void)
{for (int i = 0; i < 1000000; ++i)++f.y;
}
在这里,sum_a
可能需要不断地从主存储器(而不是从缓存)重新读取x,即使inc_b
并发修改y
是无关紧要的。
如果你还是不能理解虚假共享不要紧看下面的例子
理解虚假分享
为了更好地理解这一点,我们假设一个假设的情况:
有三位画家。每个人都有他自己的木板,他们在上面绘画,每个板有三个部门,分别是1区,2区和3区。
画家只能画出这三个部门中的一个。当画家描绘他的木板的一个部分时,另外两个板也必须改变以反映第一个画家所做的事情。
这里的木板类似于缓存块,画家类似于并发线程,绘画类似于写入活动。
请记住,此更新在逻辑上是不必要的,因为每个画家使用的分区不与其他画家使用的分区相交。可以做的是在所有画家完成绘画之后,最后可以更新木板。但这不是我们的计算机架构的工作方式。这是因为管理高速缓存机制的组件不知道实际更新了高速缓存块的哪个分区。它标记整个块为脏。强制内存更新以维持缓存一致性。与高速缓存块中的写入活动相比,这是非常昂贵的计算。
只有当写入进程和两个并行线程具有交叉缓存块时才会出现此问题。现在解决此问题的唯一方法是确保两个并行线程具有不同的缓存块。
参考:虚假分享
要实现线程数量的线性可伸缩性,我们必须确保没有两个线程同时写入同一个变量或缓存行。可以在代码级别跟踪写入同一变量的两个线程。为了能够知道自变量是否共享相同的缓存行,我们需要知道内存布局,或者我们可以使用工具告诉我们。英特尔VTune就是这样一个分析工具。下面,将解释如何为Java对象布置内存以及如何填充缓存行以避免错误共享。
上图演示虚假共享的问题。
在核心1(Core1)上运行的线程想要更新变量X,而核心2(Core2)上的线程想要更新变量Y。
不幸的是,这两个变量位于同一缓存行中。每个线程都将竞争对缓存行的所有权,以便可以更新。如果核心1获得所有权,那么缓存子系统将需要使核心2的相应缓存行置为无效。当Core 2获得所有权并执行其更新时,将告知核心1使其缓存行的副本无效。这将通过L3缓存来回乒乓,会极大的影响性能。如果竞争核心在不同的套接字上并且还必须跨越套接字互连,那么将进一步加剧性能问题。
Java内存布局
对于基于Hotspot的JVM比如现在的OpenJDK和OracleJDK,所有对象都有一个2个字的header。首先是“标记(mark)”字,其由用于散列码的24位和用于诸如锁定状态的标志的8位组成,或者它可以被交换用于锁定对象。第二个是对象类的引用。数组有一个额外的单词,用于表示数组的大小。为了提高性能,每个对象都与8字节的粒度边界对齐。因此,为了在打包时有效,根据大小(以字节为单位)将对象字段从声明顺序重新排序为以下顺序:
doubles (8) and longs (8)
ints (4) and floats (4)
shorts (2) and chars (2)
booleans (1) and bytes (1)
references (4/8)
<repeat for sub-class fields> 重复子类字段
有了这些知识,我们可以在7个长度的任何字段之间填充缓存行。为了显示性能影响,让我们花几个线程来更新自己独立的计数器。这些计数器将长期波动,可以看到它们的比较数据。
package linuxstyle.blog.csdn.net;public final class FalseSharing implements Runnable {public final static long ITERATIONS = 500L * 1000L * 1000L;public final static int NUM_THREADS = 4; // changeprivate static VolatileLong[] longs = new VolatileLong[NUM_THREADS];static {for (int i = 0; i < longs.length; i++) {longs[i] = new VolatileLong();}}private final int arrayIndex;public FalseSharing(final int arrayIndex) {this.arrayIndex = arrayIndex;}public static void main(final String[] args) throws Exception {final long start = System.nanoTime();runTest();System.out.println("duration = " + (System.nanoTime() - start));}private static void runTest() throws InterruptedException {Thread[] threads = new Thread[NUM_THREADS];for (int i = 0; i < threads.length; i++) {threads[i] = new Thread(new FalseSharing(i));}for (Thread t : threads) {t.start();}for (Thread t : threads) {t.join();}}public void run() {long i = ITERATIONS + 1;while (0 != --i) {longs[arrayIndex].value = i;}}public final static class VolatileLong {public long p1, p2, p3, p4, p5, p6; // comment outpublic volatile long value = 0L;}
}
输出如下:
结果
运行上面的代码,同时增加线程数并添加/删除缓存行填充,得到如下图所示的结果。这是测量4核测试运行的持续时间。
通过增加完成测试所需的执行时间可以清楚地看出错误共享的影响。如果没有缓存行争用,我们就可以通过线程实现近似线性扩展。
这不是一个完美的测试,因为我们无法确定VolatileLongs将在内存中的位置。它们是独立的对象。但是经验表明,同时分配的对象往往位于同一位置。
需要注意的是上面的解决办法是有争议的参考:知道你的Java对象内存布局
理论上,理论和实践是相同的
这是几年前的一篇优秀文章,它告诉大家Java应该如何布局你的对象,总结一下:
- 对象在内存中对齐8个字节(如果A%K == 0,则地址A为K对齐)
- 所有字段都是类型对齐的(long / double是8对齐,整数/ float 4,short / char 2)
- 字段按其大小的顺序打包,除了最后的引用
- 类字段永远不会混合,所以如果B扩展A,B类的对象将首先在A的字段中布局在内存中,然后是B的
- 子类字段以4字节对齐开始
- 如果类的第一个字段是long / double并且类起始点(在标题之后,或者在super之后)不是8对齐,则可以交换较小的字段以填充4字节间隙。
JVM不仅仅按照你告诉它的顺序依次对你的字段进行plok的原因也在文章中讨论,总结如下:
- 未对齐访问是不好的,因此JVM可以避免错误的布局(对内存的未对齐访问会导致各种不良副作用,包括在某些体系结构上崩溃您的进程)
- 字段的朴素布局会浪费内存,JVM重新排序字段以改善对象的整体大小
- JVM实现要求类型具有一致的布局,因此需要子类规则
那么......很好的明确规则,可能会出错?
https://gist.github.com/nitsanw/5594570#file-gistfile1-java
首先,规则不是JLS的一部分,它们只是实现细节。如果您阅读Martin Thompson关于虚假共享的文章, 您会注意到T先生有一个错误共享的解决方案,该解决方案适用于JDK 6,但不再适用于JDK 7.以下是两个版本。
下面是避免在JDK 6/7上进行错误共享:
// No false sharing on 6, but happens on 7
public final static class VolatileLong
{public volatile long value = 0L;public long p1, p2, p3, p4, p5, p6;
}
// No false sharing on 6 or 7
public static class PaddedAtomicLong extends AtomicLong
{public volatile long p1, p2, p3, p4, p5, p6 = 7L;
}
事实证明,JVM改变了它对6到7之间的字段进行排序的方式,这足以打破这个咒语。公平地说,没有上面规定的规则要求字段顺序与它们被定义的顺序相关联,但是......它需要担心并且它可以让你绊倒。
正如上述规则在我的脑海中仍然是新鲜的,LMAX 开源的Disruptor发布了Coalescing Ring Buffer。我仔细阅读了代码并发现以下内容:
public final class CoalescingRingBuffer<K, V> implements CoalescingBuffer<K, V> {private volatile long nextWrite = 1; // <-- producer access (my comment)private volatile long lastCleaned = 0; // <-- producer access (my comment)private volatile long rejectionCount = 0;private final K[] keys;private final AtomicReferenceArray<V> values;private final K nonCollapsibleKey = (K) new Object();private final int mask;private final int capacity;private volatile long nextRead = 1; // <-- consumer access (my comment)private volatile long lastRead = 0; // <-- consumer access (my comment)...
}
在介绍CoalescingRingBuffer的博客文章中找到了Nick Zeeb, 并提出了担忧,即生产者/消费者访问的字段可能会遭受错误的共享,Nick的回复:
试图对字段进行排序,以便最大限度地减少错误共享的风险。Java 7可以重新排序字段。使用Martin Thompson的PaddedAtomicLong进行了性能测试,但没有在Java 7上获得性能提升。
尼克很聪明,并不是在这里引用这些用来来批评他。引用他来表明这是令人困惑的东西(所以在某种程度上,我引用他来安慰自己与其他同样困惑的专业人士的公司)。我们怎么知道?这是我和尼克交谈后想到的一种方式:
public class FalseSharingTest {@Testpublic void test() throws NoSuchFieldException, SecurityException{long nextWriteOffset = UnsafeAccess.unsafe.objectFieldOffset(CoalescingRingBuffer.class.getDeclaredField("nextWrite"));long lastReadOffset = UnsafeAccess.unsafe.objectFieldOffset(CoalescingRingBuffer.class.getDeclaredField("lastRead"));assertTrue(Math.abs(nextWriteOffset - lastReadOffset) >= 64);}
}
使用Unsafe我可以从对象引用中获取字段偏移量,如果2个字段小于高速缓存行,则它们可能遭受错误共享(取决于内存中的结束位置)。当然,这是验证事物的一种hackish方式,但它可以成为您构建的一部分。
热门
大约在同一时间LMAX发布了CoalescingRingBuffer,Gil Tene(Azul的CTO)发布了HdrHistogram。现在Gil非常认真,非常聪明,并且比大多数人更了解JVM(这是他的InfoQ演讲,观看它)所以我很想看看他的代码。你知道什么,一堆热门领域:
public abstract class AbstractHistogram implements Serializable {// "Cold" accessed fields. Not used in the recording code path:long highestTrackableValue;int numberOfSignificantValueDigits;int bucketCount;int subBucketCount;int countsArrayLength;HistogramData histogramData;// Bunch "Hot" accessed fields (used in the the value recording code path) here, near the end, so// that they will have a good chance of ending up in the same cache line as the counts array reference// field that subclass implementations will add.int subBucketHalfCountMagnitude;int subBucketHalfCount;long subBucketMask;...
}
Gil在这里做的很好,他试图让相关领域在内存中挤在一起,这将提高他们在同一缓存行上结束的可能性,从而为CPU节省潜在的缓存。可悲的是,JVM还有其他计划......
所以这里有另一个工具可以帮助你理解你的内存布局,以便添加到你的工具带中:Java Object Layout 我偶然碰到了它,而不是一直想着内存布局。
注意histogramData如何跳转到botton并且subBucketMask被移到顶部,打破了我们的热门束。解决方案是丑陋但有效的,将所有字段移动到另一个毫无意义的父类:
abstract class AbstractHistogramColdFields implements Serializable {// "Cold" accessed fields. Not used in the recording code path:long highestTrackableValue;int numberOfSignificantValueDigits;int bucketCount;int subBucketCount;int countsArrayLength;HistogramData histogramData;
}
public abstract class AbstractHistogram extends AbstractHistogramColdFields {// Bunch "Hot" accessed fields (used in the the value recording code path) here, near the end, so// that they will have a good chance of ending up in the same cache line as the counts array reference// field that subclass implementations will add.int subBucketHalfCountMagnitude;int subBucketHalfCount;long subBucketMask;...
}
优秀的JOL现已在OpenJDK下发布。它甚至比以前更好,并支持许多时髦的功能。
http://openjdk.java.net/projects/code-tools/jol/
代码工具:jol
JOL(Java Object Layout)是分析JVM中对象布局方案的微型工具箱。这些工具大量使用Unsafe,JVMTI和Serviceability Agent(SA)来解码实际的 对象布局,占用空间和引用。这使得JOL比依赖堆转储,规范假设等的其他工具更准确。
参考:
- 易于理解虚假分享
- C ++今日博客,虚假分享再次点击!
- Dobbs博士的文章:消除虚假分享
- 在尝试消除Java中的错误共享时要小心
- Bolosky,WJ和Scott,ML 1993. 虚假共享及其对共享内存性能的影响
相关文章:

delphi xe 文件服务器,DelphiXE7中创建WebService(服务端+客户端)
相关资料:http://www.2ccc.com/news/Html/?1507.htmlhttp://www.dfwlt.com/forum.php?modviewthread&tid922DelphiXE7新建WebService具体操作:1.打开“DelphiXE7”->“File”->“New”->“Other”2.“New Items”->“Delph…

Android app 别用中文名
/************************************************************************** Android app 别用中文名* 说明:* 本来想分析一下这份源代码,结果发现因为项目名中有中文不能自动生成R* 文件,于是不想分析了。** …
一线互联网常见的14个Java面试题,你颤抖了吗程序员
跳槽不算频繁,但参加过不少面试(电话面试、face to face面试),面过大/小公司、互联网/传统软件公司,面糊过(眼高手低,缺乏实战经验,挂掉),也面过人࿰…

复化梯形公式,Newton-Cotes公式,变量代换后的复化梯形公式,Gauss-Legendre公式,Gauss-Jacobi公式插值积分的精确度比较
1.问题 分别计算积分 Ic∫01cosxxdx1.809048475800...I_c\int_0^1\frac{\cos{x}}{\sqrt{x}}dx1.809048475800... Ic∫01xcosxdx1.809048475800... Is∫01sinxxdx0.620536603446I_s\int_0^1\frac{\sin{x}}{\sqrt{x}}dx0.620536603446 Is∫01xsinxdx0.62053…

Elasticsearch 知识点目录
2019独角兽企业重金招聘Python工程师标准>>> 经过一段时间的编写,完成了第一个版本的Elasticsearch书籍的编写,目录结构如下: 1 Elasticsearch入门 7 1.1 Elasticsearch是什么 7 1.1.1 Elasticsearch是什么 7 1.1.2 Elasticsearch…

不要千言万语,一组漫画让你秒懂最终一致性
直接上图 如果你以前看过最终一致性的定义那么你一定会为这幅精彩漫画拍手叫好。 你要是不知道什么是最终一致性你可以看看下面的权威定义,当然了网上关于什么是最终一致性的帖子铺天盖地,也许你已经很明白了,即使这样你是不是依然为此图欢呼…

Feign实现服务调用
上一篇博客我们使用ribbonrestTemplate实现负载均衡调用服务,接下来我们使用feign实现服务的调用,首先feign和ribbon的区别是什么呢? ribbon根据特定算法,从服务列表中选取一个要访问的服务; RoundRobinRule:轮询RandomRule:随机Availability…

度量,跟踪和日志记录
今天,我有幸参加了2017年的分布式追踪峰会,其中有很多来自AWS / X-Ray,OpenZipkin,OpenTracing,Instana,Datadog,Librato等公司的人员,我很遗憾我忘记了这一点。有一次讨论转向了项目…

python 第六章 函数 pta(1)
1.Multiple-Choice 1.print(type(lambda:3))的输出结果是____。 A.<class ‘function’> B.<class ‘int’> C.<class ‘NoneType’> D.<class ‘float’> 答案:A 2.在Python中,对于函数定义代码的理解,正确的理解…

生成.a文件步骤
1.新建一个Project 选择 iOS->Framework & Library ->Cocoa Touch Static Library点击Next-> 输入Product Name 2.删除自动生成的文件 替换成我们需要的文件 如:原本自定生成的文件为继承自NSObject的,而你需要的为继承自UIView的ÿ…
机器学习之优雅落地线性回归法
在统计学中,线性回归(Linear regression)是利用称为线性回归方程的最小二乘函数对一个或多个自变量和因变量之间关系进行建模的一种回归分析维基百科。简单线性回归当只有一个自变量的时候,成为简单线性回归。简单线性回归模型的思…

SpringBoot整合Grpc实现跨语言RPC通讯
什么是gRPC gRPC是谷歌开源的基于go语言的一个现代的开源高性能RPC框架,可以在任何环境中运行。它可以有效地连接数据中心内和跨数据中心的服务,并提供可插拔的支持,以实现负载平衡,跟踪,健康检查和身份验证。它还适用…

python 第六章 函数
1.函数的定义 def 名称(形参): 函数体 2.函数的调用 名称(实参) 单独文件:模块 调用方式——模块.名称 3.函数的参数类型 1.位置参数: def add(a,b):add(2,3) #顺序,个数,数据类型都要相同!!…

C++简单使用Jsoncpp来读取写入json文件
一、源码编译 C操作json字符串最好的库应该就是jsoncpp了,开源并且跨平台。它可以从这里下载。 下载后将其解压到任意目录,它默认提供VS2003和VS2010的工程文件,使用VS2010可以直接打开makefiles\msvc2010目录下的sln文件。 工程文件提供Json…

BZOJ 3420: Poi2013 Triumphal arch
二分答案 第二个人不会走回头路 那么F[i]表示在i的子树内(不包括i)所需要的额外步数 F[1]0表示mid可行 k可能为0 #include<cstdio> #include<algorithm> using namespace std; int cnt,n,mid,F[300005],last[300005]; struct node{int to,next; }e[600005]; void a…

Java泛型使用需要小心
这是源自实际开发的一个坑,只是被我简化了。 Set<Integer> gs null;Set gss new HashSet();gs gss;gss.add("19");System.out.println(gs);for (int i : gs) {if (i19) {System.out.println("1");}} 代码经过一些转换你如果不注意以…

证明实对称正定矩阵A的Gauss-Seidel法必定收敛(完整过程)
Solution: \quad将nnn阶实对称矩阵AAA设为D−L−LTD-L-L^TD−L−LT,其中DDD是AAA的所有主对角元素构成对角矩阵,−L-L−L是AAA的所有主对角线以下的元素构成的严格下三角矩阵。 \quad此时Gauss−SeidelGauss-SeidelGauss−Seidel法的迭代矩阵为(D−L)−1LT(…

5月中旬的一些总结
考完英语口语了,最大的帮助就是找到了练习的方法和思路。 周三晚上有谷歌的全球IO大会。 ******** 写吴斌老师的课程作业,这才发现winedt过期了。用了rept之后本来是解决问题了,可是一联网就又不行了。总要关上再打开。用防火墙阻断却找不到选…

项目总结10:通过反射解决springboot环境下从redis取缓存进行转换时出现ClassCastException异常问题...
通过反射解决springboot环境下从redis取缓存进行转换时出现ClassCastException异常问题 关键字 springboot热部署 ClassCastException异常 反射 redis 前言 最近项目出现一个很有意思的问题,用户信息(token)储存在redis中;在获取token,反序列…

Rouche Theorem(Stein复分析)
Rouche Theorem: \quadIffandgareholomorphicfunctionsinaregionΩcontainingacircleCanditsinterior,and∣f(z)∣≥∣g(z)∣forz∈C,fandfghavethesamenumbersofzerosinsidethecircleC.If\quad f\quad and\quad g\quad are\quad holomorphic\quad functions\quad i…

Java线上程序频繁JVM FGC问题排障与启示
线上Java程序的JVM频繁FGC,现象如图所示: 一直持续FGC 5次左右,每次耗时1秒多不等。 FGC的原因实际上是内存不够用,但是运维反映堆内存是2G,从运维提供的参数看也是。 内存实际上一直只用到1G以内。 这时候可以自己写…

python常用数据结构的常用操作
作为基础练习吧。列表LIST,元组TUPLE,集合SET,字符串STRING等等,显示,增删,合并。。。 #List shoplist [apple,mango,carrot,banana] print I have ,len(shoplist), items to purchase. print These items are: for …

h5 和native 交互那些事儿
前端菜菜一枚,写下关于h5 和native 交互那些事情。偏前端,各种理论知识,不在赘述。之前有各位大牛已经写过。我只写代码,有问题,下面留言/* 关于h5 和native 之间的交互 JSBridge 解决问题,偏向前端* 使用U…
手把手教你写电商爬虫-第二课 实战尚妆网分页商品采集爬虫
系列教程 手把手教你写电商爬虫-第一课 找个软柿子捏捏 如果没有看过第一课的朋友,请先移步第一课,第一课讲了一些基础性的东西,通过软柿子"切糕王子"这个电商网站好好的练了一次手,相信大家都应该对写爬虫的流程有了一…

Python程序设计 第六章 函数(续
复习 1. 10进制 ⇒\Rightarrow⇒ 2进制 除2取余,从低位到高位存储到字符串中,从高位到低位def d2b(n):if n>1:d2b(n//2)print(n%2,end)d2b(4)出口: 条件,值确定 (一)return (二)函数体执行结…

K8S的横向自动扩容的功能Horizontal Pod Autoscaling
K8S 作为一个集群式的管理软件,自动化、智能化是免不了的功能。Google 在 K8S v1.1 版本中就加入了这个 Pod 横向自动扩容的功能(Horizontal Pod Autoscaling,简称 HPA)。 HPA 与之前的 Deployment、Service 一样,也属…

第八周例行报告
此作业要求参见:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2326 1、本周PSP 类型 任务 开始时间 结束时间 中断时间 Delta时间 会议 事后诸葛亮会议 11.3 14:12 11.3 15:08 0min 56min 博客 编写博客《事后诸葛…

HTTP头部信息解释分析(详细整理)
这篇文章为大家介绍了HTTP头部信息,中英文对比分析,还是比较全面的,若大家在使用过程中遇到不了解的,可以适当参考下 HTTP 头部解释 1. Accept:告诉WEB服务器自己接受什么介质类型,*/* 表示任何类型&#…

深圳杯---深圳市生活垃圾处理社会总成本分析
2017年3月18日,国务院向全国发布了《生活垃圾分类制度实施方案》,这标志着中国垃圾分类制度建设开始了一个全新阶段,垃圾分类已成为推进社会经济绿色发展、提升城市管理和服务水平、优化人居环境的重要举措。为了保证这一目标能够顺利实现&am…

你真的掌握了并发编程volatile synchronized么?
先看代码: import java.util.concurrent.atomic.AtomicInteger;/**** author xialuomantian*/ public class NewTest {static volatile int a 1;static volatile int b 1;//static int a 1;//static int b 1;public static AtomicInteger aa new AtomicInteg…