摘自虚拟机设计与实现 6.1 什么是线程
由于线程支持多个层级,当讨论到一个线程时,应该指出它位于哪个层级上,一个层级上的单个线程可能包含更高级上的多个线程
现实中没有必要构造太多层级的线程,通常不超过3级。第二级共享第1级的硬件上下文,第3级共享第二级的软件上下文
在Linux设计中,内核线程(软件线程)以M:N映射复用硬件上下文,glibc的用户线程以1:1映射使用内核线程上下文。有些系统在用户线程和内核线程之间使用M:N或者M:1映射,比如GNU Portable Threads 和Windows Fiber。但是这些特性要么不常用,要么只用于特殊情况
注意,在这里进程是一个无关紧要的概念,尽管进程常常和线程相混淆。线程主要是关于执行的控制流,而进程主要是关于内存空间隔离。如果两个先成运行在隔离的内存空间中,可以认为他们是运行在不同的进程中。在Linux内核中,因为所有的任务共享内核内存空间,所以在严格意义上说,在内核级别没有进程,只有内核线程。进程只存在于用户空间,用户空间为每个进程建立了隔离的虚拟内存你空间。在内核上下文中讨论进程也不是错误的,但这里进程实际上是指1:1映射到用户进程的内核线程。
摘自虚拟机设计与实现 8.1 栈展开
栈展开是指虚拟机枚举目标线程的栈内容的过程,通常涉及识别栈上方法帧的栈帧枚举过程,以及识别每个方法帧内容的栈槽枚举过程。这个过程从栈顶开始,因为这是当前栈指针指向的位置。我们知道栈指针是线程上下文的一部分,而线程上下文可以被线程直接访问。
异常处理需要栈展开,他需要运行时递归的展开栈帧,知道在某个方法内找到catch块,否则他就是未捕获异常,可能需要操作系统来处理,然后控制流从异常抛出点转移到异常处理点。
对象追踪垃圾回收器需要通过站展开找到运行时栈的根引用。调试器需要通过栈展开检查栈内容。方法调用的返回也可以被看做栈展开的一种特殊情况,它展开一个帧并把控制从被调用方转移到调用方,但通常不把这称为栈展开。栈展开通常是指运行时服务,但函数返回通常不涉及运行时,而是返回指令的硬件功能。
GC支持
java代码中支持GC,主要任务是让JIT编译器生成安全点。安全点可能包含一下位置。他们可能触发回收,阻塞线程执行,或者导致线程长时间执行。每个安全点都需要一个GC-map数据结构支持根集枚举,其中存储执行上下文中哪些位置包含引用的相关信息。
1.对象分配点 2.调用点 3.阻塞点 4.循环中 5.异常抛出点
JVM是如何调用Java方法的:JVM是C写的,JAVA字节码编译后是机器码
C语言有函数指针,通过函数指针,C可以将一个变量直接执行一个函数的首地址。C语言被编译时,C函数将被直接编译成机器指令,而这个函数指针将直接只想这段机器指令的首地址。
于是在JVM中,在源代码编译阶段就定义好一段机器指令,然后直接讲一个C函数指针指向这段机器指令的首地址,从而间接实现C语言直接调用机器指令的目的。
其实C语言还有一种办法可以调用汇编指令,那就是内嵌汇编的方式,在Linux操作系统内核中就有大量的这种用法,但是这种用法与JVM要实现的目标稍微有点不同,JVM要实现直接由C语言调用机器指令,而内嵌汇编的方式只实现了这个目标的一般,内嵌汇编的方式只能实现由C语言直接调用汇编指令
在JVM内部也有这么一个函数指针,就是call_stub。这个函数指针正式JVM内部C语言程序与机器指令的分水岭,JVM在调用这个函数之前,主要执行C程序,而JVM通过这个函数指针调用目标函数之后就直接进入了机器指令的领域。
由于物理机器不能识别java程序,也不能直接执行java程序,因此JVM必然要通过自己作为一座桥梁连接到Java程序,并让Java被调用的函数的堆栈能够寄生在JVM的某个函数的堆栈空间中,否则物理机器不会自动为Java方法分配堆栈。
JVM选择CallStub这一函数指针作为JVM内部的C/C++程序与Java程序的分水岭,当JVM启动后,执行完JVM自身的一系列指令后,能够跳转到执行Java程序经翻译后所对应的二进制机器指令,CallStub能够实现机器逻辑指令上的联机额,同时,JVM会调用Java的入口函数main,并将main主函数的入参传递进去,
因此在分水岭之后,JVM需要为主函数分配堆栈空间,以在主函数中读取入参数据,那么Java函数所需要的堆栈空间分配在哪里呢?答案是明显的,既然CallStub()作为分水岭函数,很自然的JVM将Java函数堆栈空间“寄生”在了CallStub()函数堆栈中。当然,从技术实现的手段而言,
JVM并非一定要选择寄生这种方式,JVM完全可以另外定义一种算法接口来支持java函数的调用机制,但是JVM并没有,那么如何实现“寄生”,这需要依靠物理机器提供的指令,对CallStub()堆栈进行扩展。
物理机器提供了扩展堆栈空间的指令:
sub operand ,%sp
operand是一个自然数,例如8,16或者其他数值,这条指令表示将堆栈向下扩展一定的空间,如果你写了一个C/C++函数,编译器会自动计算一个函数所需的堆栈大小并分配堆栈空间。CallStub作为JVM内部C/C++和Java的分水岭,他是在JVM启动过程中动态生成的,他会计算堆栈大小并生成对应的分配合适大小堆栈的机器码



