java需要知道的计算机底层
计算机组成
cpu
PC
Program Counter,程序运行的时候,需要指令和数据,而一个复杂的程序有很多指令,那么每一步执行哪一条指令,就从内存里取出来放到PC了里,记住这个指令在内存里位置,从而知道下一个指令从内存的哪里取。
Registers
寄存器,一颗cpu里有很多寄存器,有的写,有的读,还有其他的作用,程序运行时的数据就放到寄存器里。
ALU
Arithmetic & Logic Unit,计算和逻辑单元,比如计算2+3,从PC里拿+指令,从register A里拿2,从register B里拿3,数据,运算后把结果再返回给register C,然后刷回内存。
cache
缓存,cpu里的io速度是内存里的100倍,如果每次都从内存里拿数据,会很慢,于是就有了缓存。结构如下:
cpu里的缓存分三级,各级的读取速度如下:
对于多核cpu,在各核里有各自的L1和L2缓存,在同一颗cpu里公用L3缓存,计算机读取IO是块读的,即每次读取一个缓存行(cache line)每次都是64K,那么假如2核cpu,同时需要读取了同一个缓存行,而核1改了该缓存行里的某些值,为了保证数据一致性,要通知核2这个缓存行里的值被改了,通过缓存一致性协议(MSI MESI(intel的) MOSI Synapse Firefly Dragon等)
为了避开缓存一致性协议,有一种变成叫做“缓存行对齐”编程,比如jdk7里有很多变量用7个long(8字节)填充,再声明变量,再用7个long填充,保证该变量在一个缓存行里,读取就快了。而jdk8有@Contended注解,在声明变量的时候,加上这个注解,jvm就保证这个变量单独放到一个缓存行里。jvm要加 -XX:-RestrictContended注解才起作用。
超线程CPU
我们的一个ALU一次只能执行一个线程,如果是单线程,cpu在运算时需要把数据和指令拿到Registers和PC里进行运算,那么其他的线程再运算时就需要清除数据和指令,把需要计算的拿进来计算,完成后再把原来的没计算完成的数据和指令再拿进来计算。就比较慢。比如四核八线程,就是一个ALU对应两组PC和registers,一次性把两个线程里的数据和指令拿到不同组的registers和PC里,cpu在计算的时候,ALU只需要在不同的组直接切换就能完成计算,就快得多。
电脑开机过程
电脑开机过程
NUMA
Non Uniform Memory Access的简称,对应的是UMA(Uniform Memory Access)。
UMA,一般的PC是CPU、内存插在主板上固定的地方,所有内核都去这个地方访问内存。
NUMA,有的服务器的架构,在硬件上分成某租CPU有物理上离他最近的内存,物理离得近就访问快,内核优先访问这部分内存,找不到对应的资源了再去其他的内存里访问。
乱序执行
cpu执行不同的指令的时间片消耗不同,为了提高执行效率,cpu会在某种规则上不按照代码的顺序执行。如果要顺序执行,cpu层面,intel有原语(Mfence->Mixd Fence->混合栅,IFence->Input Fence->读栅,SFence->Save Fence->写栅来锁定某块资源(比如内存)来保证不同指令对这块资源的顺序执行),很遗憾,jvm因为是跨硬件平台的,所以只能用跨平台的lock指令来实现,比如java的volatile关键字,是用四个内存屏障(LoadLoad,LoadStorage,StorageLoad,StorageStorage)来实现的。比如一个读和一个写的指令存在并发,jvm在这个内存前后加上两个内存屏障,保证如果写的执行在前,就一定保证写的指令执行完了后续的读写指令才能访问这块内存,从而保证内存可见性,也防止了乱序执行。
微内核
传统的PC即,内核程序需要管理硬件、内存、进程调度等,微内核就把传统的内核的这些工作分开,只关注与程序调度,效率高,可插拔,模块化,华为的HM就是这么干的。
用户态与内核态
在最原始的DOS系统的时候,内核和用户程序都可以访问计算机的资源,比如硬件的第一扇区(Master Boot Record,放内核程序的地方),用户程序可以改这个地方,电脑就被黑了,黑客盛行。现在的linux内核分为内核态和用户态,内核态管理硬件等,用户程序想要操作硬件,必须通过内核程序取访问,而有些敏感的资源用户程序是无法访问的,保证系统安全。
进程线程纤程
以a.exe程序执行为例,a这个程序是放在磁盘里的,双击后就load到内存里,操作系统分配了一个这个程序的资源空间,即开启了一个进程。再双击一次a,在a这个程序不要求一个操作系统只有一个程序的前提下,又分配给给a另外的资源,有开启一个进程。这两个进程有独立的内存空间。
a程序代码里不只一个线程,在cpu运算过程中,在这些线程之间切换,即线程调度,a程序里的多个线程共享a这个进程的内存空间。
多线程高并发的程序运行过程中,需要频繁的切换用户态和内核态,硬件上,单线程的cpu需要频繁的清除寄存器(Regiesters)和程序计数器(Process Counter),多线程的cpu需要在这些Registers和PC之间切换,大部分的时间小号在切换用户态和内核态。所以有了只在用户态运行的线程,即纤程,当然,不能切换成内核态,所以纤程适用于计算时间短但是cpu占用很高的运算。
开辟一个线程大概占1M内存,开辟一个纤程序占4K内存,开10万个线程电脑可能卡死,开10万个纤程电脑运行正常。
目前内置纤程序的语言:Kotlin、Scala、Go、引用了特定库的Python,jdk在实验阶段的有(openJdk+loom)。
中断
进程有实时进程和普通进程,实时进程(优先级1~99)的优先级永远比普通进程高(-20~19),进程调度的时候优先执行实时进程。
默认进程调度算法。CMS(Completely Fair Scheduler,按优先级分配cpu时间,记录每个进程的执行时间,如果一个进程的执行时间不到他应该分配的时间,在下一次调度时优先执行)绝对公平算法和RR(Round Robin,每个进程执行时间一样,轮换这执行)轮循算法,优先级有差异的用CMS,同样优先级的RR。
中断分为硬中断(来自硬件,比如键盘和网络)和软中断(来自程序内部,比如程序里调用read()指令),优先级高于除了内核不允许的某些实时进程之外的所有进程。是一个信号,在内核里就是int 0x80,所有的内核接口最多五个参数,通过ax寄存器传入中断调用好(比如一个read中断),bx、cx、dx、si、di传入参数,计算万了再把结果写回ax寄存器,一层一层返回给程序。
僵尸进程和孤儿进程
linux的进程标记是PCB,假如父进程fork了多个子进程,子进程的PCB由父进程管理,如果子进程退出了,系统回收了子进程的资源,但是父进程还运行,意味着子进程的PCB还在,ps命令能开到这些进程还在(被打了defunct的进程),这些子进程就叫做僵尸进程。不可以kill掉,但是可以kill掉父进程。假如父进程死了,子进程还在,那么父进程会把子进程的PCB交给他的父进程(所有进程的父进程id号是1,带图形界面的进程的父进程是14多少,但是这个14多少的父进程还是1),那么这些子进程就是孤儿进程。可以kill掉孤儿进程,
反正进程执行完了会被回收分配的资源,不影响性能,kill不kill的无所谓。
总结
以上是生活随笔为你收集整理的java需要知道的计算机底层的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: mysql自定义变量
- 下一篇: 电脑开机过程