2026-04-08 09:53:49
JVM内存模型即运行时数据区,在JVM1.8中,主要由方法区(元数据区)、堆、虚拟机栈、本地方法栈和程序计数器组成,其中堆和方法区是线程共享的,虚拟机栈、本地方法栈、程序计数器是线程隔离的。
作用:存放虚拟机加载的类型信息,域(Field)信息,方法(Method)信息,常量,静态变量,即时编译器编译后的代码缓存。方法区中存在运行时常量池,字面量、符号引用等存放入其中。
内存溢出:无法申请到内存时,将抛出OutOfMemoryError。
Hotspot演变:
Java6及之前:方法区存在永久代,保存有静态变量。
Java7:进行去永久代工作,虽然还保留着,但静态常量池,如字符串常量池,已经移动到堆中。
Java8:移除永久代,类型信息、域(Field)信息、方法(Method)信息存放在元数据区;字符串常量池、静态变量存放在堆区。
较为完整的JVM内存模型图如下:

单独的JVM内存模型图如下:

作用:保存每一次方法调用的信息。每个Java线程创建时,都会创建对应的虚拟机栈,每一次方法调用,都会往栈中压入一个栈帧。
栈帧内容:
局部变量表:保存函数的局部变量。
操作数栈:保存计算过程中的结果,即临时变量。
动态链接:指向方法区的运行时常量池。字节码中的方法调用指令以常量池中指向方法的符号引用为参数。
方法的返回地址。
栈帧图如下:

作用:记录线程执行的字节码行号,如果当前线程正在运行native方法则为空,也有称之为PC寄存器。
重要性:字节码解释器在工作时,通过改变计数器的值来选取下一跳需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基本功能都需要依赖计数器来完成。Java虚拟机通过轮流切换并分配处理器执行时间实现多线程,在任意确定的时间点,一个处理器只会处理一个线程中的指令,为了正确地处理线程切换后的任务恢复,每一个线程都具有自身的程序计数器。
作用:提供类实例和数组的内存。
划分:
新生代:亦可称之年轻代、New Generation,包括Eden区、Survivor区(S0和S1中存在互相移动,一些文章中的from、to是指移动上的逻辑关系,S0和S1是Survivor区的两个子区)。
老年代:Old Generation。
堆内存划分图如下:

对象创建与GC关系:
新生成的对象在Eden区。
触发Minor GC后,还“幸存”的对象移动到S0。
再次触发Minor GC后,S0和Eden中存活的对象被移动到S1中,S0清空。
每次移动时,自动递增计数器,超过默认值时(印象中是16),移动到老年代,如果Eden中没有足够内存分配,也将直接在老年代中分配内存。
老年代中依靠Major GC。

Java线程的实现方式有三种,分别是使用内核线程实现、使用用户线程实现、使用用户线程加轻量级进程混合实现,具体如下:
实现方式:完全依赖操作系统内核提供的内核线程(Kernel-Level Thread ,KLT)来实现多线程,线程的切换调度由系统内核完成。程序一般使用轻量级进程(Light Weight Process, LWP)这种高级接口,用户进程中通过LWP使用系统的内核线程,由于其一对一的关系,又称为一对一模型。
优点:用户线程与LWP一一对应,LWP是独立的调度单元,某个LWP在用户进程调用过程中发生阻塞,以及在系统调用中发生了阻塞,都不会影响整个进程的执行。
缺点:LWP依托内核线程,线程操作需要依赖系统调用,代价较高,需要在用户态(User Mode)和内核态(Kernel Mode)中来回切换;每个LWP都需要一个内核线程进行支持,要消耗一定的内核资源,一个系统仅可支持少量有限的LWP。
内核线程模型图如下:

实现方式:JVM平台实现用户线程(User Thread,UT),完全自行实现创建、调度、销毁,线程的调度不再依赖内核,极少占据内核资源,基本限定在用户态内。
优点:可以突破量的限制,并且减少线程切换时的损耗。
缺点:难以利用多核CPU的优势,并且一旦产生系统调用发生中断,其他线程也将被中断,这种多对一模型的实用性较低。
用户线程模型图如下:

实现方式:又称多对多模型,既存在UT,也存在LWP。创建、切换线程(UT)依旧是廉价的,并且可以拥有大量的线程;同时利用LWP作为UT到KLT(内核线程)的桥梁,享受了系统内核的线程调度、CPU映射,免去了自行实现系统调用的部分。
优点:进行系统调用时,阻塞整个进程的概率低于用户线程模型。
混合模型图如下:
