Java内存—栈区

概述

栈是随着程序创建的每一个线程而生成的。它是通过线程关联的。每一个线程都有自己的栈。所有局部变量及函数调用都存放在栈中。它的生命周期依赖于线程的生命周期,线程活着它就活着,反之亦然。栈的内存不需要是连续的。它的大小可以是固定值或者动态扩展。Java虚拟机只在Java栈上进行两种操作:压栈与入栈。

一个特定的线程栈也可以称之为:运行时栈。该线程执行的每一个方法调用都存储在对应的运行时栈中,包括参数、局部变量、中间计算以及其他数据。一个方法完成以后,将从栈中删除对应的条目。当所有的方法调用都完成以后,处于空栈状态,并且在线程结束之前,这个空栈会被JVM立即删除。存储在线程中的数据对于线程自身是可用的,而对于其他的线程则不可用。

栈中的每一个条目我们都称之为:栈帧或活动记录。

栈帧的结构

  • 每一帧都包含它自己的局部变量数组(Local Variable Array——LVA)、帧数据(Frame Data——FD)以及操作数栈(Operand Stack——OS)。
  • LVA、FD和OS的大小都是在编译期内确定的
  • 在一个线程控制中,每次只有一帧是活动的,称为当前帧。

自定义栈大小

如果我们没有为栈指定大小,JVM将使用一个默认值创建栈。通常,其默认值依赖于操作系统和计算机体系结构。例如,下面列出java14+的在一些系统及结构下的默认值:

  • Linux/86(64-bit):1MB
  • macOS(64-bit):1MB
  • Oracle Solaris(64-bit):1MB
  • 在Windows平台上,JVM使用系统级的栈大小(见globals_windows_x86.hpp),即默认值从320K/1MB【32bit/64bit】,依赖于操作系统的版本而有所改变。

基本上,我们可以确认在大多数的现代操作系统和体系结构下每一个栈的的大小都是1MB左右。

为了改变栈的大小,我们可以使用-Xss参数标识。例如,-Xss1048576所设置的栈大小就是1M(1024*1024=1048576字节),下面都是正确设置栈大小的方式:

-Xss1m
-Xss1024k
-Xss1048576

-XX:ThreadStackSize与-Xss的区别

与-Xss类似,我们也可以使用-XX:ThreadStackSize参数来设置栈的大小,但是二者是有区别的。

单位不同

-Xss的最小单位是字节(B);而-XX:ThreadStackSize的最小单位是千字节(KB)。

-XX:ThreadStackSize=1024
或
-XX:ThreadStackSize=1k
由于其最小单位是kb,则其大小为1024*1024=1M
相当于
-Xss1m
或
-Xss1024k

-Xss与-XX:ThreadStackSize混用出现的不同

我们编写一个很小的程序来进行验证:

public class Main {
    public static void main(String[] args) {
        System.out.println("Thread test!");
        try{
            for(int i=1;i<102400;i++){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try{
                            Thread.sleep(1000000);
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                }).start();

                System.out.println("i=" + i);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

该程序是在主线程中使用循环语句创建102400个子线程,每个子线程创建后保持睡眠状态。

1、主线程启动只会以-Xss设置的大小为基础,而无视-XX:ThreadStackSize的设置

java -jar -Xss1 -XX:ThreadStackSize=100 -XX:MaxMetaspaceSize=10m -XX:-UseCompressedClassPointers -Xmx100m simple-em/target/simple-em-1.0.0-SNAPSHOT-full.jar

程序会直接报错而无法启动:

The Java thread stack size specified is too small. Specify at least 180k
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

下面我们将参数改变一下顺序,看看会发生什么:

java -jar -XX:ThreadStackSize=100 -Xss1 -XX:MaxMetaspaceSize=10m -XX:-UseCompressedClassPointers -Xmx100m simple-em/target/simple-em-1.0.0-SNAPSHOT-full.jar

程序依然无法启动,而错误的原因和上面的是一样的。那么也就是说主线程启动的时候只会读取-Xss所设置的栈大小并根据该值启动自身,而无视于-XX:ThreadStackSize设置的值。

2、除主线程外,其他线程的线程栈设置原则——谁最后出现谁说了算

下面我们将-Xss的值设置得大一些,以便使主线程正常运行。

java -jar -Xss1m -XX:ThreadStackSize=100 -XX:MaxMetaspaceSize=10m -XX:-UseCompressedClassPointers -Xmx100m simple-em/target/simple-em-1.0.0-SNAPSHOT-full.jar

此时程序正常运行,但是由于我们的内存设置其实并不能支撑程序启动102400个线程,但是它却不会退出。虽然主线程在运行,但是其实程序已经报警告,我们杀掉主线程,会发现程序的警告,如下:

[6.892s][warning][os,thread] Failed to start thread "Unknown thread" - _beginthreadex failed (EACCES) for attributes: stacksize: 100k, flags: CREATE_SUSPENDED STACK_SIZE_PARAM_IS_A_.
[6.893s][warning][os,thread] Failed to start the native thread for java.lang.Thread "Thread-30364"
Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached
        at java.base/java.lang.Thread.start0(Native Method)
        at java.base/java.lang.Thread.start(Thread.java:802)
        at com.dokbok.study.simpleem.Main.main(Main.java:19)

我们可以看出,每个线程分配的线程栈大小为100k,这是我们通过命令中的-XX:ThreadStackSize=100设置的值,说明-XX:ThreadStackSize=100的设置是生效的。

我们再次将命令的顺序进行变更:

java -jar -XX:ThreadStackSize=100 -Xss1m -XX:MaxMetaspaceSize=10m -XX:-UseCompressedClassPointers -Xmx100m simple-em/target/simple-em-1.0.0-SNAPSHOT-full.jar

使用同样的方式使主线程停下来,我们看到的警告如下:

[6.029s][warning][os,thread] Failed to start thread "Unknown thread" - _beginthreadex failed (EACCES) for attributes: stacksize: 1024k, flags: CREATE_SUSPENDED STACK_SIZE_PARAM_IS_A.
[6.029s][warning][os,thread] Failed to start the native thread for java.lang.Thread "Thread-30206"
Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached
        at java.base/java.lang.Thread.start0(Native Method)
        at java.base/java.lang.Thread.start(Thread.java:802)
        at com.dokbok.study.simpleem.Main.main(Main.java:19)

可以看到,每个线程栈分配的大小变成了1024k,这正是通过命令中的-Xss1m设置的。这说明,每个线程分配的线程栈的大小是-Xss与-XX:ThreadStackSize的最后出现的顺序所决定的。

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注