JVM运行时内存管理之线程私有(生命周期和线程相同)

在JVM初识中提到之所以在程序和操作系统之间增加JVM,就是JVM有些内存管理的特性直接在操作系统上实现有些费劲。那么JVM的内存管理是怎样的呢?其中内存部分分运行时数据区和非运行时数据区,即虚拟机的内存结构。具体可参考下图:

其中线程私有的:1)程序计数器 2)虚拟机栈 3)本地方法栈

线程共享的:1)堆 2)元数据区

直接内存也是线程共享,但其不属于运行时数据区的一部分。本文主要介绍线程私有内存:

一、计数器PC Register:也叫PC寄存器,是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。

二、Java虚拟机栈(Java Virtual Machine Stacks)

Java虚拟机栈和线程同时创建,用于存储栈帧。每个方法在执行时都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直到执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

该结构可配置大小,可以优化的一个点:

-Xss 为jvm启动的每个线程分配的内存大小:

Linux/x64 (64-bit): 1024 KB macOS (64-bit): 1024 KB Oracle Solaris/x64 (64-bit): 1024 KB Windows: The default value depends on virtual memory -Xss1m -Xss1024k -Xss1048576

局部变量:

操作数栈:操作数栈(Operand Stack)也称作操作栈,是一个后入先出栈(LIFO)。随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。

动态连接:

方法返回地址:

方法返回地址存放调用该方法的PC寄存器的值。一个方法的结束,有两种方式:正常地执行完成,出现未处理的异常非正常的退出。无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用者的PC计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。但无论方法是否正常完成,都需要返回到方法被调用的位置程序才能继续进行。

三、Native Method Stacks:本地方法栈(Native Method Stacks) 与虚拟机栈所发挥的作用是非常相似的, 其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码) 服务, 而本地方法栈则是为虚拟机使用到的本地(Native) 方法服务。

在Java虚拟机规范中,对本地方法栈这块区域,与Java虚拟机栈一样,规定了两种类型的异常: (1)StackOverFlowError :线程请求的栈深度>所允许的深度。 (2)OutOfMemoryError:本地方法栈扩展时无法申请到足够的内存。

参考JVM调优维护之常用命令中关于jstack命令,可以获取以上两个stack的信息。

关于运行时参数设置,可参考官网https://docs.oracle.com/en/java/javase/17/docs/specs/man/java.html#advanced-runtime-options-for-java