大家好,我是编哥。今天来一篇原创科普小文,看看Jvm的内存模型。这个是系列文章,如果大家喜欢,后面咱们会继续这个主题哦~
Amdahl定律 正逐渐代替 摩尔定律
摩尔定律,通俗的说就是:每18到24个月,集成电路上可容纳的元器件数目便会增加一倍,芯片的性能也会随之翻一番。
而Amdahl定律是一个计算机科学界的经验法则,因吉恩·阿姆达尔而得名。它代表了处理器并行运算之后效率提升的能力。
当摩尔定律被认为即将失效,硬件性能快要到达极限时,Amdahl定律发挥了作用,并发处理的广泛应用,能最大效率地榨干我们所用机器的运算资源。
多任务(并发)的起源
经常在新闻上看到,什么超级计算机又每秒多少亿运算,但是我们看视频,刷剧可能还是不时会卡。
因为我们现在使用的设备,cpu的运算速度比存储以及网络通信子系统相差非常大。
为了在看视频卡顿的时候,你还能看看PDF,用个计算器算账,多任务就出现了。
另外,在服务端编程这块,多任务是必须的,因为你的软件要同时为很多用户服务,比如说双十一的秒杀,每个顾客的请求,都是计算机要去完成的一个任务。
充分利用计算机的处理能力
说白了,就是程序员要充分利用计算机的处理能力,让业务组件的TPS(transaction per second)足够高。
并发的挑战和福音
写好并发程序是困难的,好在 Java语言和虚拟机提供了非常多并发的工具,降低了并发编程的门槛,让上层编码人员关注业务本身。
当我们在上层熟练业务之余,不妨来了解一下支持我们在业务中,进行并发编程的基石。
硬件的效率和一致性
为了提升效率,我们找房子就先去找中介,而不是一个个房子敲门去问。
同样,cpu为了更高效于存储,通信子系统沟通,就出现了高速缓存。
我们知道,操作系统是管理硬件资源的,那么管理包含 高速缓存 的这种操作系统,就是共享内存多核系统。
这种操作系统下的真实机器 的内存模型(memory model),就会是这个样子:
处理器->高速缓存->缓存一致性协议->主内存
还是上面中介的例子,你可以把 高速缓存, 缓存一致性协议理解为:中介小哥,和中介小哥们在的公司。
缓存一致性协议保证了cpu在访问高速缓存时的正确性,它包含几种典型的协议:
• MSI
• MESI 这是一种常用的缓存一致性协议
• MOSI
• Synapse
• Dragon Protocol
内存模型的定义 由此引出
内存模型就是:特定的操作协议下,对内存和高速缓存的读写访问的过程抽象。
类比找房子,你是去链家还是贝壳或者安居客呢?他们有差不多的业务流程和服务风格,尽管有些微差异,但都能帮你找到房子。
屏蔽一些恼人的细节
如果你听说过 cpu的乱序执行,jvm的即时编译器也有指令重排序 这两件事情,那么你会好奇:指令不是我在代码里写的那种执行顺序,程序还能正确出结果?
是的,可以!
内存模型就有这一方面的功效,我们先看看 JMM 的历史。
JMM初识
太古早的历史我们不去深究,你需要知道的是,从JDK 5 实现了 JSR-133 这个提案的要求后,Java 内存模型才叫一个成熟。
这是Java面世九年后,在2004年才搞定的,为啥这么慢?因为这个模型
1) 必须严谨
内存访问不会有任何歧义, 不然bug满天飞可就毁了Java这门语言以及后面庞大的生态。
2) 又要宽松
Java本身是要跨平台的,而JVM就是它跨平台的地基。
JVM自己本身非常希望充分利用物理机器的各种优良特性,来取得在这台机器上的更好性能,所以它不得不在多种机器上进行适配调整。
如此Java语言就能在各种机器上有相同的行为表现,不至于出现在C/C++生态里面的,“在我这种机器上可以跑啊,怎么到你哪就各种编译不过,跑着跑着就segment fault 了” 的问题。
主内存 和 工作内存
如果你记得我上面提过的物理机器增加高速缓存后的内存模型,那么这里我们来做一个对比:JMM(Java Memory Model)类比物理机器的内存模型。
物理机器增加高速缓存后的内存模型:
处理器->高速缓存->缓存一致性协议->主内存
JMM:
Java线程->工作内存->Save和Load操作->主内存
请记住,内存是变量的大池子。
当我们 回到 内存模型,就会明白内存模型的实质目的:内存模型就是 定义你写的各种变量的访问规则。
包括:
• 实例中的各种字段,属性变量
• 静态字段
• 数组里面的元素
但是不包括:
• 局部变量(方法里面单独声明的,而非类的成员)
• 方法参数(方法括号里面的,这是方法调用是,放入各个线程自己的执行栈的)
因为这些是线程私有的,不存在读写竞争。
在理清所有的访问规则前,我们需要知道,变量的访问(读写)在哪里发生?
对于JMM,这一切在主内存和工作内存发生。
主内存
你知道,JVM虚拟机跑起来时,本身会在物理机上占用一块内存,然后它会再划分出一块内存,当作JMM的主内存。
工作内存
虚拟机里面有许多活动的线程,而每个线程拥有自己的工作内存,里面存储了被该线程使用的变量的主内存副本。
线程实际上操作的变量,全是在工作内存里,而不是主内存。
不同线程之间,不会打扰对方的工作内存,不会互相访问。
线程他们总是通过主内存进行变量的同步。
volatile 变量看起来直接驻留在主内存(因为每次读写总是取得最新结果),但实际上也会在线程的工作内存有副本。(特殊的操作顺序性保证了它总是崭新的状态)
和我们通常理解的Java内存区域的异同
如果你了解过Java虚拟机的内存划分,那么你知道它的运行时数据区,分为堆内存,虚拟机栈,本地方法栈,PC程序计数器,方法区。
这和JMM有关系吗?
其实没有任何关系[手动狗头]
但是你硬要联系起来记忆的话,你可以这样理解:
主内存,对应Java堆中的实例的数据部分,存储在物理机的主内存中。
但是不对应堆里面保存的其他数据,说的就是不包括:
* MarkWord
* 对象hash码
* GC标志
* GC年龄
* 同步锁信息
* KlassPoint
* Padding
而 工作内存 对应 虚拟机栈的部分区域,反映在物理实体上,就是存储在物理机的寄存器和高速缓存中。
再看看这个图,是不是清晰了很多:
下篇预告
在本篇中,我们分享了一些Java内存模型的起源,定义,概览。下一篇就是具体的规则,正是这些规则,保证了我们并发编程的正确性。
如果您感兴趣,欢迎分享给其他小伙伴,谢谢!
end
我们爬虫第三期来了,加入我们,学更实用,更值钱的 Python 技术!
从0到1系统掌握Python 技术(入门进阶)
2个企业实战项目,4大常用工具
掌握24种反爬策略手段,成为真正爬虫高手
能抓取市面上90%的网站
掌握主流爬虫技术,就业找工作 真正全方位帮助大家从0到1,从 Python 入门到进阶,转行找爬虫工作。