一文理解JVM線程屬于用戶態(tài)還是內(nèi)核態(tài)
Linux操作系統(tǒng)的體系架構(gòu)分為用戶態(tài)和內(nèi)核態(tài)(或者用戶空間和內(nèi)核)。

用戶態(tài)與內(nèi)核態(tài)
內(nèi)核從本質(zhì)上看是一種軟件——控制計算機(jī)的硬件資源,并提供上層應(yīng)用程序運(yùn)行的環(huán)境。用戶態(tài)即上層應(yīng)用程序的活動空間,應(yīng)用程序的執(zhí)行必須依托于內(nèi)核提供的資源,包括CPU資源、存儲資源、I/O資源等。
注:對操作系統(tǒng)來說,用戶態(tài)線程具有不可見性,也稱透明性。
用戶態(tài)線程調(diào)度完全由進(jìn)程負(fù)責(zé),通常就是由進(jìn)程的主線程負(fù)責(zé)(用戶可以為應(yīng)用程序定制調(diào)度算法),相當(dāng)于進(jìn)程主線程的延展,使用的是操作系統(tǒng)分配給進(jìn)程主線程的時間片段;內(nèi)核線程由內(nèi)核維護(hù),由操作系統(tǒng)調(diào)度。
用戶態(tài)線程無法跨核心,一個進(jìn)程的多個用戶態(tài)線程不能并發(fā),阻塞一個用戶態(tài)線程會導(dǎo)致進(jìn)程的主線程阻塞,直接交出執(zhí)行權(quán)限。這些都是用戶態(tài)線程的劣勢。內(nèi)核線程可以獨(dú)立執(zhí)行,操作系統(tǒng)會分配時間片段。
用戶態(tài)的應(yīng)用程序可以通過三種方式來訪問內(nèi)核態(tài)的資源:
系統(tǒng)調(diào)用
公用函數(shù)庫
Shell腳本
為什么需要區(qū)分用戶態(tài)和內(nèi)核態(tài)
在 CPU 的所有指令中,有些指令是非常危險的,如果錯用,將導(dǎo)致系統(tǒng)崩潰,比如清內(nèi)存、設(shè)置時鐘等。如果允許所有的程序都可以使用這些指令,那么系統(tǒng)崩潰的概率將大大增加。
所以,CPU將指令分為特權(quán)指令和非特權(quán)指令,對于那些危險的指令,只允許操作系統(tǒng)及其相關(guān)模塊使用,普通應(yīng)用程序只能使用那些不會造成災(zāi)難的指令。
比如Intel的CPU將特權(quán)等級分為4個級別:Ring0~Ring3;Linux 系統(tǒng)只使用了Ring0和Ring3兩個運(yùn)行級別。
當(dāng)進(jìn)程運(yùn)行在Ring3級別時被稱為運(yùn)行在用戶態(tài),而運(yùn)行在 Ring0 級別時被稱為運(yùn)行在內(nèi)核態(tài)。
從用戶態(tài)到內(nèi)核態(tài)的切換的時機(jī)

很多程序開始時運(yùn)行于用戶態(tài),但在執(zhí)行的過程中,一些操作需要在內(nèi)核權(quán)限下才能執(zhí)行,這就涉及到一個從用戶態(tài)切換到內(nèi)核態(tài)的過程。
系統(tǒng)調(diào)用。操作系統(tǒng)對內(nèi)核級別的指令進(jìn)行封裝,統(tǒng)一管理硬件資源,然后向用戶程序提供系統(tǒng)服務(wù),用戶程序進(jìn)行系統(tǒng)調(diào)用后,操作系統(tǒng)執(zhí)行一系列的檢查驗(yàn)證,確保這次調(diào)用是安全的,再進(jìn)行相應(yīng)的資源訪問操作。比如C函數(shù)庫中的內(nèi)存分配函數(shù)malloc(),它具體是使用sbrk()系統(tǒng)調(diào)用來分配內(nèi)存;當(dāng)malloc調(diào)用sbrk()的時候就涉及一次從用戶態(tài)到內(nèi)核態(tài)的切換;類似的函數(shù)還有printf(),調(diào)用的是wirte()系統(tǒng)調(diào)用來輸出字符串等等。
異常事件。當(dāng)CPU正在執(zhí)行運(yùn)行在用戶態(tài)的程序時,突然發(fā)生某些預(yù)先不可知的異常事件,這個時候就會觸發(fā)從當(dāng)前用戶態(tài)執(zhí)行的進(jìn)程轉(zhuǎn)向內(nèi)核態(tài)執(zhí)行相關(guān)的異常事件,如缺頁異常。
外圍設(shè)備的中斷。當(dāng)外圍設(shè)備完成用戶的請求操作后,會向CPU發(fā)出中斷信號,此時,CPU就會暫停執(zhí)行下一條即將要執(zhí)行的指令,轉(zhuǎn)而去執(zhí)行中斷信號對應(yīng)的處理程序,如果先前執(zhí)行的指令是在用戶態(tài)下,則自然就發(fā)生從用戶態(tài)到內(nèi)核態(tài)的轉(zhuǎn)換。
注意:系統(tǒng)調(diào)用的本質(zhì)其實(shí)也是中斷,相對于外圍設(shè)備的硬中斷,這種中斷稱為軟中斷,這是操作系統(tǒng)為用戶特別開放的一種中斷。所以,從觸發(fā)方式和效果上來看,這三種切換方式是完全一樣的,都相當(dāng)于是執(zhí)行了一個中斷響應(yīng)的過程。但是從觸發(fā)的對象來看,系統(tǒng)調(diào)用是進(jìn)程主動請求切換的,而異常和硬中斷則是被動的。
理解了這里,推薦看下《NIO效率高的原理之零拷貝與直接內(nèi)存映射》中有關(guān)NIO零拷貝優(yōu)化的知識。
用戶線程與內(nèi)核線程的映射關(guān)系
用戶線程一般不會直接去使用內(nèi)核線程,而是去使用內(nèi)核線程的一種高級接口——輕量級進(jìn)程(Light Weight Process,LWP)。因此對于用戶線程來說,用戶程序必須讓它的調(diào)度器采用用戶線程,然后在內(nèi)核線程上運(yùn)行它。
用戶線程與內(nèi)核線程的映射關(guān)系有三種模型:一對一模型、多對一模型、多對多模型。
一對一模型

有了內(nèi)核線程,每個用戶線程被映射到一個內(nèi)核線程。用戶線程在其生命期內(nèi)都會映射到該內(nèi)核線程。一旦用戶線程終止,兩個線程都將離開系統(tǒng)。這被稱作"一對一"線程映射。
缺點(diǎn):
操作系統(tǒng)限制了內(nèi)核線程的數(shù)量,因此一對一模型會使用戶線程的數(shù)量受到限制。
操作系統(tǒng)內(nèi)核線程調(diào)度時,上下文切換的開銷較大,導(dǎo)致用戶線程的執(zhí)行效率下降。
多對一模型

多對一模型將多個用戶線程映射到一個內(nèi)核線程上,線程之間的切換由用戶態(tài)的代碼來進(jìn)行,因此相對一對一模型,多對一模型的線程切換速度要快許多。此外,多對一模型對用戶線程的數(shù)量幾乎無限制。
缺點(diǎn):
如果其中一個用戶線程阻塞,那么其它所有線程都將無法執(zhí)行,因?yàn)榇藭r內(nèi)核線程也隨之阻塞了。
在多處理器系統(tǒng)上,處理器數(shù)量的增加對多對一模型的線程性能不會有明顯的增加,因?yàn)樗械挠脩艟€程都映射到一個處理器上了。
多對多模型

多對多模型(又稱為M對N模型)結(jié)合了一對一模型和多對一模型的優(yōu)點(diǎn),將多個用戶線程映射到多個內(nèi)核線程上。
優(yōu)點(diǎn):
一個用戶線程的阻塞不會導(dǎo)致所有線程的阻塞,因?yàn)榇藭r還有別的內(nèi)核線程被調(diào)度來執(zhí)行。
多對多模型對用戶線程的數(shù)量沒有限制。
在多處理器的操作系統(tǒng)中,多對多模型的線程也能得到一定的性能提升,但提升的幅度不如一對一模型的高。在現(xiàn)在流行的操作系統(tǒng)中,大都采用多對多的模型。
JVM線程屬于用戶態(tài)還是內(nèi)核態(tài)
java線程在jdk1.2之前,是基于名為“綠色線程”的用戶線程實(shí)現(xiàn)的,這導(dǎo)致綠色線程只能同主線程共享CPU分片,從而無法利用多核CPU的優(yōu)勢。
由于綠色線程和原生線程比起來在使用時有一些限制, jdk1.2中放棄綠色線程,轉(zhuǎn)而使用原生線程。
在目前的jdk版本中,操作系統(tǒng)支持怎樣的線程模型,很大程度上決定了java虛擬機(jī)的線程是怎樣映射的,這點(diǎn)在不同的平臺上都沒有辦法達(dá)成一致。
總的來說就是,虛擬機(jī)規(guī)范中并沒有限定java線程需要使用哪種線程模型,要根據(jù)不同的平臺來說,但是無論使用哪種線程模型,java程序的編碼和運(yùn)行都是沒有差異的。
例如,Java SE最常用的JVM是Oracle/Sun研發(fā)的HotSpot VM。在這個JVM所支持的所有平臺上都是采用一對一的線程模型的,除了Solaris平臺。
參考文檔:
《深入理解Java虛擬機(jī)》
