一文理解JVM线程属于用户态还是内核态
Linux操作系统的体系架构分为用户态和内核态(或者用户空间和内核)。
用户态与内核态
内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程序运行的环境。用户态即上层应用程序的活动空间,应用程序的执行必须依托于内核提供的资源,包括CPU资源、存储资源、I/O资源等。
注:对操作系统来说,用户态线程具有不可见性,也称透明性。
用户态线程调度完全由进程负责,通常就是由进程的主线程负责(用户可以为应用程序定制调度算法),相当于进程主线程的延展,使用的是操作系统分配给进程主线程的时间片段;内核线程由内核维护,由操作系统调度。
用户态线程无法跨核心,一个进程的多个用户态线程不能并发,阻塞一个用户态线程会导致进程的主线程阻塞,直接交出执行权限。这些都是用户态线程的劣势。内核线程可以独立执行,操作系统会分配时间片段。
用户态的应用程序可以通过三种方式来访问内核态的资源:
系统调用
公用函数库
Shell脚本
为什么需要区分用户态和内核态
在 CPU 的所有指令中,有些指令是非常危险的,如果错用,将导致系统崩溃,比如清内存、设置时钟等。如果允许所有的程序都可以使用这些指令,那么系统崩溃的概率将大大增加。
所以,CPU将指令分为特权指令和非特权指令,对于那些危险的指令,只允许操作系统及其相关模块使用,普通应用程序只能使用那些不会造成灾难的指令。
比如Intel的CPU将特权等级分为4个级别:Ring0~Ring3;Linux 系统只使用了Ring0和Ring3两个运行级别。
当进程运行在Ring3级别时被称为运行在用户态,而运行在 Ring0 级别时被称为运行在内核态。
从用户态到内核态的切换的时机
很多程序开始时运行于用户态,但在执行的过程中,一些操作需要在内核权限下才能执行,这就涉及到一个从用户态切换到内核态的过程。
系统调用。操作系统对内核级别的指令进行封装,统一管理硬件资源,然后向用户程序提供系统服务,用户程序进行系统调用后,操作系统执行一系列的检查验证,确保这次调用是安全的,再进行相应的资源访问操作。比如C函数库中的内存分配函数malloc(),它具体是使用sbrk()系统调用来分配内存;当malloc调用sbrk()的时候就涉及一次从用户态到内核态的切换;类似的函数还有printf(),调用的是wirte()系统调用来输出字符串等等。
异常事件。当CPU正在执行运行在用户态的程序时,突然发生某些预先不可知的异常事件,这个时候就会触发从当前用户态执行的进程转向内核态执行相关的异常事件,如缺页异常。
外围设备的中断。当外围设备完成用户的请求操作后,会向CPU发出中断信号,此时,CPU就会暂停执行下一条即将要执行的指令,转而去执行中断信号对应的处理程序,如果先前执行的指令是在用户态下,则自然就发生从用户态到内核态的转换。
注意:系统调用的本质其实也是中断,相对于外围设备的硬中断,这种中断称为软中断,这是操作系统为用户特别开放的一种中断。所以,从触发方式和效果上来看,这三种切换方式是完全一样的,都相当于是执行了一个中断响应的过程。但是从触发的对象来看,系统调用是进程主动请求切换的,而异常和硬中断则是被动的。
理解了这里,推荐看下《NIO效率高的原理之零拷贝与直接内存映射》中有关NIO零拷贝优化的知识。
用户线程与内核线程的映射关系
用户线程一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程(Light Weight Process,LWP)。因此对于用户线程来说,用户程序必须让它的调度器采用用户线程,然后在内核线程上运行它。
用户线程与内核线程的映射关系有三种模型:一对一模型、多对一模型、多对多模型。
一对一模型
有了内核线程,每个用户线程被映射到一个内核线程。用户线程在其生命期内都会映射到该内核线程。一旦用户线程终止,两个线程都将离开系统。这被称作"一对一"线程映射。
缺点:
操作系统限制了内核线程的数量,因此一对一模型会使用户线程的数量受到限制。
操作系统内核线程调度时,上下文切换的开销较大,导致用户线程的执行效率下降。
多对一模型
多对一模型将多个用户线程映射到一个内核线程上,线程之间的切换由用户态的代码来进行,因此相对一对一模型,多对一模型的线程切换速度要快许多。此外,多对一模型对用户线程的数量几乎无限制。
缺点:
如果其中一个用户线程阻塞,那么其它所有线程都将无法执行,因为此时内核线程也随之阻塞了。
在多处理器系统上,处理器数量的增加对多对一模型的线程性能不会有明显的增加,因为所有的用户线程都映射到一个处理器上了。
多对多模型
多对多模型(又称为M对N模型)结合了一对一模型和多对一模型的优点,将多个用户线程映射到多个内核线程上。
优点:
一个用户线程的阻塞不会导致所有线程的阻塞,因为此时还有别的内核线程被调度来执行。
多对多模型对用户线程的数量没有限制。
在多处理器的操作系统中,多对多模型的线程也能得到一定的性能提升,但提升的幅度不如一对一模型的高。在现在流行的操作系统中,大都采用多对多的模型。
JVM线程属于用户态还是内核态
java线程在jdk1.2之前,是基于名为“绿色线程”的用户线程实现的,这导致绿色线程只能同主线程共享CPU分片,从而无法利用多核CPU的优势。
由于绿色线程和原生线程比起来在使用时有一些限制, jdk1.2中放弃绿色线程,转而使用原生线程。
在目前的jdk版本中,操作系统支持怎样的线程模型,很大程度上决定了java虚拟机的线程是怎样映射的,这点在不同的平台上都没有办法达成一致。
总的来说就是,虚拟机规范中并没有限定java线程需要使用哪种线程模型,要根据不同的平台来说,但是无论使用哪种线程模型,java程序的编码和运行都是没有差异的。
例如,Java SE最常用的JVM是Oracle/Sun研发的HotSpot VM。在这个JVM所支持的所有平台上都是采用一对一的线程模型的,除了Solaris平台。
参考文档:
《深入理解Java虚拟机》