全网最硬核Java程序员必备底层知识(一)

简介: 全网最硬核Java程序员必备底层知识(一)

一、引言

对于Java开发者而言,关于底层知识,我们一般当做黑盒来进行使用,不需要去打开这个黑盒。

但随着目前程序员行业的发展,我们有必要打开这个黑盒,去探索其中的奥妙。

本篇系列文章,将带你一起探索底层黑盒的奥秘之处。

二、相关书籍推荐

读书的原则:不求甚解,观其大略

67fbc9f779cd424eb8af46b1eda9a770.png


你如果进到庐山里头,二话不说,蹲下头来,弯下腰,就对着某棵树某棵小草猛研究而不是说先把庐山的整体脉络研究清楚了,那么你的学习方法肯定效率巨低而且特别痛苦。


最重要的还是慢慢地打击你的积极性,说我的学习怎么那么不happy啊,怎么那么没劲那,因为你的学习方法错了,大体读明白,先拿来用,用着用着,很多道理你就明白了。


《编码:隐匿在计算机软硬件背后的语言》

《深入理解计算机系统》(不建议读)

《算法导论》、《Java数据结构和算法》、《剑指offer》

《30天自制操作系统》

《TCP/IP详解》卷一

龙书《编译原理》

三、硬件基础知识

1、CPU的制作过程

CPU是如何制作的?

我相信每一个人都会都这么一个问号,今天来告诉你,所有的CPU都来源于:沙子

对于CPU的制作流程,这里有篇文章,大家有兴趣的可以看一下:CPU是如何制作的

没有兴趣的直接看概括:

  • 第一步:我们从沙子中提供单晶硅 晶体
  • 第二步:将晶体切割成薄片,得到 晶圆
  • 第三步:将金属粒子轰击到晶圆上,再进行 电镀
  • 第四步:在晶圆上进行 光刻,完成不同晶体管之间的导线互连
  • 第五步:质量检测,去除质量差的CPU

2、CPU的原理

计算机最开始要解决的问题:如何代表数字?

最原始的计算机采用的是利用灯泡,当我们计算 0100 + 1010 时,我们使用 8 个灯泡,以 灯泡的状态来代表 0 和 1,这样我么的第一版的计算机已经OK了。


ENIAC重达27吨,占地1800平方英尺(约合167.2平方米)。诞生于二战时期,最初是作为辅助炮兵计算炮弹轨迹的工具。



image.png

第一版的计算机,有一个让人哭笑不得的缺点。

我们在对计算机进行高速计算时,灯泡的闪光频次比较高,有可能造成灯泡的损坏,就需要有工作人员及时的更换灯泡,从而影响效率。

作为最初的一款计算机来说,他的面世足够让地球人震撼。

目前的计算机大都采取晶体管的方式进行计算,利用 与门或门非门或非门 的状态去表示不同的计算方式。

我们经常在生活中听说,CPU32位、CPU64位,简单来说,他们的区别就是:一次性读取多少位(bit)的数字

我们任何的计算,都可以通过逻辑运算来进行得到,我们来看下面这个逻辑运算:0 && 1 = 0,是怎么实现的?

首先,我们看一下电路图:这是一个 与门 电路图

对于该电路而言,AB 作为输入,Q 作为输出。

例如 A 输入低电平、B 输出高电平,那么 Q 就会输出低电平,转换为二进制就是 A 输入0、B 输出1,那么 Q 就会输出0,对应的逻辑运算表达式为 0 && 1 = 0

这里有一个关于BUG来源的小故事:从前有一个人,进行计算机的计算时,

发现数字总是不正确,找了好久也没找到原因,后来发现是计算机有个孔被虫子(BUG)腐蚀了,导致没办法进行低电平、高电平的切换,从此,我们编程上的错误就叫做:BUG

3、汇编语言的执行过程

我们想一下,在上面电路中,发生了 0 && 1 = 0 这样的事件,我使用者怎么知道机器发生了这种事件呢?

我们不可能直接把机器拆开,看里面的电平变换吧。

所以,在这里就出现了 汇编语言,而汇编语言的本质也是作为机器语言的助记符 出现的

比如,我们给计算机说,你去给我计算 1 + 2 这个操作,计算机需要进行高低电平的差异输出计算结果

而我们的汇编语言:movaddsub

我们可以一目了然的了解目前计算机的操作状态

计算机的组成图:



376d9e2f766840019682c153a99f80df.png

我们看一下计算机的计算的整个流程:

这里要说明一下 JavaC 语言的区别:

  • C语言:直接可以让CPU进行编译
  • Java语言:需要让 JVM 翻译,才能让 CPU 编译,这也正是 Java 跨平台的关键所在

4、量子计算机

对于量子计算机而言,目前世界上都在进行探索,暂无成果

在我们普通的计算机中,一个比特代表 1 或者 0,32个比特,可以代表 2^32 的任何一个数字

而我们的量子比特,最亮眼地方在于,他可以同时表示10

  • 一位量子比特:1、0
  • 二位量子比特:00、01、10、11
  • 三位量子比特:000、001、011、111…
  • 三十二位量子比特:一次性表示 2 ^ 32 的数字

这样描述可能不太直观,我们看一个例子:

现在有一个数字,我们知道该数字范围为: 1~2^32,我们怎么能快速求出该数字呢?

对于普通比特而言,一次只能表示一个,所以我们需要循环遍历 2^32 次,才可以找到该数字

而对于量子比特而言,直接使用 32 位的操作系统即可完成

5、CPU的基本组成

  • PC(Programme Counter):程序技术器当前指令的地址
  • Registers:寄存器,暂时存储CPU计算需要的数据
  • ALU(Arithmetic & logic Unit):运算单元,做运算使用
  • CU(Control Unit):控制单元
  • MMU(memory Mangagement unit):内存处理单元
  • Cache:缓存

5.1 ALU



2defa6627ac94f98b7c19eb31efdd225.png

之前的CPU属于单核情况,这样的话,会只有一个 Registers,我们的 PC 会不断的进行切换来指向新的线程,将所对应的数据存放到 Registers,对于切换(context switch)而言,会严重影响我们的效率。

现在的CPU通常是多核状态,在进行计算时,我们会有两个以上 Registers,这样的话,我们的PC就不需要频繁的进行切换,我们的 ALU 处理计算的切换即可。

5.2 寄存器

5.3 Cache

我们通过上面的图可以看出,我们的计算机为了获取数据的方便性,增加了三级缓存,对于不同的缓存,获取的时间的长短也是不一样的

对于多核CPU来说,如下图所示:

  • L1、L2存储在不同的核中
  • L3存储在同一个 CPU

5.3.1 局部性原理

简单来说,我们的CPU在读取数据时,将数据按快读取,不单独取一个字节,如下图所示:

当前的CPU需要 X 这个目标值,步骤如下:

  • 第一步:去寄存器里寻找,有没有 X 这个字段
  • 第二步:去 L1、L2、L3 Cache 去寻找 X 这个字段
  • 第三步:去内存、磁盘等寻找 X 这个字段
  • 第四步:找到后,将以 X 开头的 64个字节 形成一个块
  • 第五步:在 L3、L2、L1 中分别存入这个数据,方便下次去拿缓存
  • 第六步:将 X 写入寄存器,进行数据处理、

5.3.2 MESI Cache一致性协议

我们可以看到,对于上述两个核的 L1、L2 的缓存行要保持一致,保持一致的协议被称为:MESI 缓存一致性协议

CPU 每个 Cache line 标记四种状态

  • M(已修改):该 Cache line 有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。
  • E(独占):该 Cache line 有效,数据和内存中的数据一致,数据只存在于本Cache中。
  • S(共享):该 Cache line 有效,数据和内存中的数据一致,数据存在于很多Cache中。
  • I(无效):Cache line 是无效的

21016a382a914bac94ac3c8839e2e1e7.png

因特尔——缓存行

  • 缓存行越大,局部性空间效率越高,但读取时间慢
  • 缓存行越小,局部性空间效率越低,但读取时间快
  • 因特尔通过实验规定,缓存行的大小为:64 字节

总线锁(缓存行装不下的情况下,就必须锁总线)

缓存锁实现之一,有些无法被缓存的数据或者跨越多个缓存行的数据,依然必须使用总线锁

我们怎么测试我们的猜想是正确的呢?

我们测试两个程序

篇幅受限,源码的话这里暂时不展示了,有兴趣的可以关注公众号,回复:算法源码

  • 第一个程序:数值的更改在同一个缓存行
  • 第二个程序:数值的更改不在同一个缓存行



0335ebdf0c75456c8933197cd4288b8f.png

  • 有上述程序验证我们的猜想,两个线程频繁的更快缓存区中的缓存快,导致运行时间加长

5.3.3 缓存行对齐

对于有些特别敏感的数字,会存在线程高竞争的访问,为了保证不发生伪共享,我们一般不要求缓存行对齐

简单来说,我们不希望我们获取 X 数字的同时,把 Y 也给获取进来

在我们 JDK7disruptor 都采取long cache line padding

public long p1, p2, p3, p4, p5, p6, p7; // cache line padding
private volatile long cursor = INITIAL_CURSOR_VALUE;
public long p8, p9, p10, p11, p12, p13, p14; //cache line padding

这样的话,当我们缓存行获取时,就会把 cursor 前面的 long 或者 后面的 long 加载到缓存块中,避免 cursor 的缓存行对齐

在我们的 JDK8 中,我们可以给该参数加入 @Contended(根据底层的CPU来进行设定,保证不会让两个参数共享一个缓存行),需要加上 -XX:-RestrictContended 生效










相关文章
|
Rust JavaScript Go
2024年十大值得关注的编程语言
探索2024年最有影响力的编程语言:Python的多功能无与伦比,JavaScript在Web领域的统治地位,Rust的高效性,等等。
|
消息中间件 JSON Java
Spring Boot、Spring Cloud与Spring Cloud Alibaba版本对应关系
Spring Boot、Spring Cloud与Spring Cloud Alibaba版本对应关系
25953 0
|
运维 安全 测试技术
【答案】2023年国赛信息安全管理与评估正式赛答案-模块3 CTF
【答案】2023年国赛信息安全管理与评估正式赛答案-模块3 CTF
【答案】2023年国赛信息安全管理与评估正式赛答案-模块3 CTF
|
存储 消息中间件 运维
从单体到微服务:架构演进中的技术挑战与解决方案
在软件开发的过程中,系统架构的选择对项目的成功与否起到至关重要的作用。本文将深入探讨从单体架构向微服务架构演进过程中所遇到的技术挑战,并提供相应的解决方案。
407 0
|
12月前
|
存储 IDE Java
如何检查类文件是否被篡改?
类文件被篡改可能导致安全问题和程序异常。检查方法包括:1. 比对文件哈希值;2. 使用反编译工具对比代码;3. 检查文件签名。确保类文件的完整性和安全性。
278 3
|
资源调度 Kubernetes 监控
Kubernetes 集群性能优化实践
【5月更文挑战第17天】在容器化和微服务架构日益普及的当下,Kubernetes 已成为众多企业的首选容器编排工具。然而,随着集群规模的增长和业务复杂度的提升,性能优化成为确保系统稳定性与高效运行的关键。本文将深入探讨 Kubernetes 集群性能优化的策略与实践,覆盖从节点资源配置到网络通信优化,再到高效的资源调度机制,旨在为运维人员提供系统的优化路径和具体的操作建议。
|
人工智能 自然语言处理 算法
开放式API在AI应用开发中的革命性角色
【7月更文第21天】随着人工智能技术的飞速发展,开放式API(Application Programming Interfaces)正逐渐成为连接技术与创新、加速AI应用开发的关键桥梁。这些API允许开发者轻松访问预先训练好的模型和复杂算法,无需从零开始构建基础架构,从而极大地降低了AI应用的开发门槛,促进了技术民主化。本文将探讨开放式API如何在AI领域引发革命性变化,通过实际案例和代码示例展现其强大功能。
526 2
|
Ubuntu 安全 测试技术
Ubuntu 22.04 Samba 安装和配置
SMB(Server Message Block)是一种跨平台的文件共享协议,它允许不同操作系统之间的文件和打印机共享。在本文中,我们将详细介绍如何在 Ubuntu 服务器上部署和配置一个 SMB 服务器,并涵盖多通道配置、性能测试、安全最佳实践以及一些常见问题。【8月更文挑战第1天】
2270 1
|
JavaScript Serverless API
Serverless Framework
Serverless Framework 是一个开源的工具框架,用于构建和部署无服务器应用程序。它提供了一组工具和功能,简化了无服务器应用程序的开发和部署过程。Serverless Framework 支持多个云平台(如函数计算、AWS Lambda、Google Cloud Functions 等),并提供了命令行工具和配置文件来定义和管理应用程序的各个组件,如函数、事件触发器、API 网关等。它还提供了自动化部署、资源管理、日志追踪等功能,使开发者能够更方便地构建和管理无服务器应用。
435 2
|
安全 C++ 数据格式
C++ 字符串格式化转为 数据变量 - sscanf,sscanf_s及其相关用法
C++ 字符串格式化转为 数据变量 - sscanf,sscanf_s及其相关用法
451 1