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自定义变量
- 下一篇: 电脑开机过程