看这篇,再不怕 JVM 内存面试题:Java 内存模型系列(一)

大家好,我是编哥。今天来一篇原创科普小文,看看Jvm的内存模型。这个是系列文章,如果大家喜欢,后面咱们会继续这个主题哦~

85850d3c1bf77c28937bcb64e2a86387.png

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

而 工作内存 对应 虚拟机栈的部分区域,反映在物理实体上,就是存储在物理机的寄存器和高速缓存中。

再看看这个图,是不是清晰了很多:

506862c586bbd0deff250121f0b1e9d6.png


下篇预告

在本篇中,我们分享了一些Java内存模型的起源,定义,概览。下一篇就是具体的规则,正是这些规则,保证了我们并发编程的正确性。

如果您感兴趣,欢迎分享给其他小伙伴,谢谢!

end

我们爬虫第三期来了,加入我们,学更实用,更值钱的 Python 技术!

975df222d8a81ddaafbb735f6c90f4f8.png

  1. 从0到1系统掌握Python 技术(入门进阶)

  2. 2个企业实战项目,4大常用工具

  3. 掌握24种反爬策略手段,成为真正爬虫高手

  4. 能抓取市面上90%的网站

  5. 掌握主流爬虫技术,就业找工作 真正全方位帮助大家从0到1,从 Python 入门到进阶,转行找爬虫工作。

34000d9733e0bcb4c8c4eee2b62bf0e8.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值