java程序员需要掌握的计算机底层知识 学习笔记(一)

本文深入探讨了计算机硬件和编程语言的基础知识,包括CPU的构成、指令执行过程、汇编语言、编译原理以及Java的编译与解释。讲解了缓存的层次结构、伪共享现象和CPU的乱序执行,同时还涉及操作系统、内存管理和并发编程中的概念,如内存屏障、线程安全以及NUMA架构。此外,文章还提到了操作系统启动流程和内核设计。
摘要由CSDN通过智能技术生成

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

《深入理解计算机系统》

语言:C    java  《C程序设计语言》《C primer Plus》

数据结构与算法:《java数据结构与算法》 《算法》 《算法导论》 《计算机程序设计艺术》

操作系统:Linux内核源码详解   30天自制操作系统

网络:机工《TCP/IP详解》卷一

编译原理: 机工  龙书《编译原理》  编程语言实现模式

数据库: SQLite源码  derby

计算机需要解决的最根本的问题,就是怎么代表数字

cpu的制作过程,cpu的原理:晶体管的工作原理《编码》17章

汇编的执行过程:

01001000  为了好记这段代码  比如这段代码表示:add

汇编的本质:助记符(可以理解为词典,将二进制编码翻译成我们可读的信息)。就是通过表来翻译二进制编码,本质就是机器语言。

计算机通电-》CPU读取内存中程序(电信号输入) -》 时针发生器不断震荡通断电 -》 推动CPU内部一步一步执行(执行多少步取决于指令需要多少的时钟周期)-》计算完成-》写回(电信号)-》DMA写给显卡输出(显卡内部有缓冲区,每个缓冲区的位置都对应屏幕的位置)

时针发生器的震动频率就是计算机主频

问题:java的解释与编译的区别?

java编译后是Bytecode二进制码,通过jvm的解释器解释为cpu的机器语言(解释执行)。c语言编译后放入内存即为机器语言,可以直接cpu执行。

cpu的二进制语言即机器语言与java语言的bytecode二进制语言有什么区别?

java为了跨平台,bytecode是java自身的二进制语言,需要jvm跟据不同的操作系统,跟据不同系统的解释器,解释为相应操作系统的机器语言,去cpu执行(跨平台原理)。

java相关硬件知识

cpu和内存是计算机硬件核心。

cpu的基本组成:

PC->programme Counter 程序计数器 : 记录当前指令地址。内存是个特别大的数组,当前指令在内存的哪个位置,需要记录指令的地址。

Registers 寄存器  :暂时存储CPU计算机需要用到的数据(从内存内拷贝临时数据到CPU内部,便于指令执行,运行计算完指令释放),  寄存器好几个,各个都有各自的功能。与JVM的栈有相似点。成本很高。

        64位CPU表示寄存器可以一次存储64的数字,ALU可以一次读64位数字。

ALU:Arithmetic & LOgicUnit  运算单元,做计算机运算。  如  内存内有两个数字 2,3,求和

        运算流程为:内存中的数字2copy到寄存器A,3copy到寄存器B。程序计数器PC记录当前指令为Add。

        ALU接收到Add命令,会到A和B取出2,3进行计算,计算完成后输出给寄存器C,最后C再将结果copy到内存的某个位置。

CU :Control UNit 控制单元

MMU : memory management unit 内存管理单元。硬件加操作系统组成。

cache : 缓存

超线程:

 一个运算单元对应多个pc和寄存器。如果不对应多个,需要线程切换。如三个线程ABC,CPU只有一个PC和寄存器,则线程A执行,执行过程中,数据暂存,切换到B线程执行B线程,这样的切换就是线程切换,上下文切换,context switch。效率比较低。

如果CPU比较强,可以有很多个PC,寄存器,线程能更快的切换。

存储器的层次结构:

 缓存的物理结构:

每个CPU内有两个核,每个核都有L1,L2,两个核共享L3.

 如果有两个CPU,则两个CPU共享内存。如图:

 缓存原理:按块读取:跟据程序局部性原理,读取同时可以读取附近的数据,可以提高效率。

                充分发挥总线CPU针脚等一次性读取更多数据的能力。

cacleline的概念:缓存行

主内存的数据-》L3 -》L2-》L1-》计算单元与寄存器

CPU内部的缓存一致性协议MESI,是以缓存行为单位的。

如:

CPU A的L2的缓存发生变动,则计算机的其它CPU的L2也要同样更新变动,重新从内存读取。

 有的数据一个缓存行装不下。缓存行不适合,保持一致性需要锁总线。缓存一致性协议有时也被成为缓存锁。终极解决方案就是锁总线,只能当前缓存行读取完,其它CPU才能访问内存。

缓存行越大,局部性空间效率越高,但读取时间慢。

缓存行越小,局部性空间效率越低,但读取时间快。

英特尔折中,一行64字节。

伪共享:

在多核架构中, 会发生一种伪共享的情况,原因是,每个cpu缓存自己的数据,cpu0对long i进行操作,缓存了i和i附近共计64字节数据;cpu1对long j进行操作,缓存了j和j附近共计64字节的数据。若i和j相邻,那么两个cpu缓存的就是主存中相同的一段64字节的内存数据。此时若cpu0对i的操作是写,导致这段地址上的数据对于其他cpu的缓存失效,cpu1尽管不操作i,也需要重新去主存中load这段数据,导致了额外的开销

由于缓存一致性协议会造成不同CPU之间的相同缓存行不停的更新,会造成性能降低。

案例:一:已知long类型长度为8,则在一个long类型的数组长度为2,分成两个线程,分别不停的更新数组的两个位置,即A线程更新index0位置,B线程更新index1。

二:long数组设置为长度为16,即总长度为128,A线程更新index0位置,B线程更新index8位置。

会发现第二种方案会比第一种方案效率更高。

已知英特尔的缓存行长度为64

因为第一个方案,两个线程的更新一直是一个缓存行,触发缓存一致性协议,影响效率。而第二个方案,由于index8超出了64长度,因此index0与index8不在同一个缓存行,所以效率更高。

缓存行对齐

对于有些特别敏感的数字,会存在线程高竞争的访问,为了保证不发生伪共享,可以使用缓存行对齐的编程方式。

JDK7中采用long padding提高效率,即加入两个长度为8的long数组。

JDK8中,加入了@Contended注解,配合 JVM: -XX:-Restrictcontended 实现缓存行对齐

CPU的乱序执行 

两个互相没有关联的指令会乱序执行。

代码:jvm/jmm/Disord.java

乱序执行会出现的问题:

DCL (Double check Lock) 双重锁单例为什么是单例volitle?到底需要不需要volatile

 由于初始化的过程中分为很多指令,存在中间态。

由于CPU的乱序执行,有可能发生指令重排序:

问题:如何解决指令重排序?

1.设置内存屏障

对某部分内存做操作时前后添加的屏障,屏障前后的操作不可以乱序执行。.

intel CPU层面硬件实现  有三个原语:lfence(读屏障) sfence(写屏障) mfence(读写屏障)

也可以使用总线锁来解决cpu指令重排序的问题。

2.有序性保障也可以通过 intel lock汇编指令实现。lock指令又为原子指令。指令是一个Full Barrier,执行时会锁住内存子系统来保证执行顺序,甚至跨多个CPU。Software Locks通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序。

3.JVM层级的内存屏障,与硬件没有任何关系。 SS,SL,LL,LS   (s:store 写 l:load 读)

volatile的实现细节,即在volatile前后加了jvm的内存屏障。

:Jvm层面

CPU指令重排序规则:只要前后两条指令没有互相依赖的关系,即可重排序乱序执行。

JMV指令重排序规则:Hanppens-before原则 JLS - java language sepicifation  8条规则

  • 程序次序规则:在一个线程内一段代码的执行结果是有序的。就是还会指令重排,但是随便它怎么排,结果是按照我们代码的顺序生成的不会变!
  • 管程锁定规则:就是无论是在单线程环境还是多线程环境,对于同一个锁来说,一个线程对这个锁解锁之后,另一个线程获取了这个锁都能看到前一个线程的操作结果!(管程是一种通用的同步原语,synchronized就是管程的实现)
  • volatile变量规则:就是如果一个线程先去写一个volatile变量,然后一个线程去读这个变量,那么这个写操作的结果一定对读的这个线程可见。
  • 线程启动规则:在主线程A执行过程中,启动子线程B,那么线程A在启动子线程B之前对共享变量的修改结果对线程B可见。
  • 线程终止规则:在主线程A执行过程中,子线程B终止,那么线程B在终止之前对共享变量的修改结果在线程A中可见。
  • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程代码检测到中断事件的发生,可以通过Thread.interrupted()检测到是否发生中断。
  • 传递规则:这个简单的,就是happens-before原则具有传递性,即A happens-before B , B happens-before C,那么A happens-before C。
  • 对象终结规则:这个也简单的,就是一个对象的初始化的完成,也就是构造函数执行的结束一定 happens-before它的finalize()方法。

 as if serial  不管如何重排序,单线程执行结果不会改变。

总结:解决CPU指令重排序方法?

CPU层面:三个原语:Lfence, Sfence, MFence 或者lock指令锁总线。

JVM层级:8个happens-before原则 + 4个内存屏障 (LL,LS, SS, SL)

as-if-serial:不管硬件什么顺序,单线程执行的结果不变,看上去像是serial

扩展问题:多个请求,要求顺序执行,怎么操作?

WC-write combining buffer合并读写技术

为了提高写效率,cpu在写入L1时,同时用wc写入L2

计算单元,寄存器与L1之间还有一个缓存单元叫:storeBuffer,空间特别小。

满四个字节,通过wc直接写入L2

由于ALU速度太快,所以在写入L1的同时,写入一个wcBuffer即storebuffer,满了之后直接写入L2

NUMA  non uniform-memory-access 非一致性内存访问

UMA,多个CPU共享一块儿内存空间   :一致性内存访问

一致性内存访问缺点:不宜扩展,cpu数量增多后引起内存访问冲突加剧。cpu的很多资源花在争抢内存地址上面。4颗CPU左右比较合适。

NUMA:指在主板插槽上,一组CPU有一个专用的memory内存。各组CPU与内存通过总线连接,也可以总线互相访问别的cpu的专用内存(效率较低)   就近访问原则。

ZGC-NUMA-aware  最近内存分配

分配内存会优先分配该线程所在CPU的最近内存

启动:

通电->biod uefi工作 ->自检 -》到硬盘固定位置加载bootloader-》读取可配置信息-》可配置信息存放于cmos

bootloader将读取到的相应的操作系统内核配置信息,读入计算机内存。

然后计算机权限交给操作系统内核。-OS

os本身就是一个软件,可以同时管理硬件(cpu,内存等)和应用(进程)

linux操作系统:《linux内核设计与实践》

 内核:

 宏内核:所有的功能都具备。这些功能程序都与内核在一块儿内存空间或者说芯片(计算机内存)

微内核:应用管理进程调度。只有进程调度与微内核在一块儿内存空间。因此微内核操作文件系统可以理解为:本身没有读硬盘的程序,需要到其它空间或芯片找到文件系统功能(可通过互联网或局域网),读完后返回给内核,内核在返回给应用。(效率低,弹性部署 5G loT)

 鸿蒙即基于微内核,弹性部署,通过互联网实现微内核互联。

各设备都有各自功能的微内核,各设备通过互联网实现互联,可以实现以下功能:

冰箱:洗碗机,把我里面的碗洗一下。

洗碗机:ok没问题,机器人,去冰箱把碗拿给我。

机器人:ok没问题

 外核:存在于科研实验室(不重要)为应用定制操作系统(多租户 request-based GC JVM定制化)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值