JVM工作原理与实战(十七):运行时数据区-栈内存溢出

简介: JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了栈内存溢出、设置虚拟机栈的大小等内容。

一、Java虚拟机栈

Java虚拟机栈是Java技术中至关重要的部分,它负责管理方法调用中的基本数据。在执行Java程序时,Java虚拟机栈是线程私有的,即每个线程都拥有自己的Java虚拟机栈,并且随着线程的创建而创建,随着线程的销毁而回收。


Java虚拟机栈,运用栈式数据结构对方法调用过程中的基本数据进行管理。遵循先进后出(First In Last Out)的原则,每一个方法调用都会对应一个独立的栈帧(Stack Frame)进行数据存储。

二、栈内存溢出

1.栈内存溢出介绍

Java虚拟机(JVM)的栈内存是用于存储方法执行时的数据和信息的区域。每个方法的执行都会在栈上创建一个新的栈帧,其中包含了方法的局部变量、操作数栈、动态链接和方法出口信息。如果一个方法调用的层级过深,或者每个栈帧占用的内存过大,那么就会导致栈内存溢出。当这种情况发生时,系统会抛出StackOverflowError异常。


在默认情况下,JVM会根据操作系统和计算机体系结构自动设置每个线程的栈大小。这样可以确保JVM能够根据不同的环境进行适当的配置。具体的默认栈大小取决于JVM实现和配置,以下是一些常见的默认值:

  • 在Linux上,对于x86(64位)体系结构,默认栈大小为1MB;对于ppc体系结构,默认栈大小为2MB。
  • 在BSD上,对于x86(64位)体系结构,默认栈大小为1MB。
  • 在Solaris上,对于64位体系结构,默认栈大小为1MB。
  • 在Windows上,默认栈大小基于操作系统的默认值。


模拟栈内存溢出案例

通过递归方式调用同一方法,且不设定退出条件,并定义一个计数器变量来跟踪每次调用的次数。随着每次调用,计数器递增。最后,查看错误发生时总调用的次数。

public class Demo1 {
    public static void main(String[] args) {
        recursion();
    }
    public static int count = 0;
    public static void recursion() {
        System.out.println(++count);
        recursion();
    }
}
image.gif

运行结果:

image.gif

2.设置虚拟机栈的大小

为了解决Java虚拟机栈内存溢出的问题,可以通过调整栈的大小来应对。要修改Java虚拟机栈的大小,可以使用虚拟机参数-Xss。通过这个参数,可以为每个线程设置一个自定义的栈大小。


语法为-Xss栈大小,单位可以是字节(默认,是 1024 的倍数)、k或者K(KB)、m或者M(MB)、g或者G(GB)。除了-Xss参数外,还有一个与它类似的参数-XX:ThreadStackSize可以用来调整堆栈大小。

案例:

public class Demo1 {
    public static void main(String[] args) {
        recursion();
    }
    public static int count = 0;
    public static void recursion() {
        System.out.println(++count);
        recursion();
    }
}
image.gif

设置虚拟机栈的大小(-Xss):

-Xss512k
image.gif

image.gif

运行结果:

image.gif

在调整堆栈大小时,还需要注意一些事项:

  • 最小和最大堆栈大小限制:不同的JVM实现和操作系统可能会有不同的最小和最大堆栈大小限制。例如,在Windows(64位)下的JDK8测试中,最小值为180k,最大值为1024m。因此,在设置堆栈大小时,需要考虑这些限制,以避免出现错误。
  • 局部变量和操作数栈深度:过多的局部变量或者过深的操作数栈深度都可能影响栈内存的大小。因此,在编写代码时,需要合理地控制局部变量的数量和操作数栈的深度,以避免出现内存溢出的情况。
  • 递归深度:虽然递归可以简化代码,但是如果没有适当的退出条件或者递归深度过大,也会导致栈内存溢出。因此,在使用递归时,需要仔细设计递归算法,并设置合适的退出条件,以避免出现内存溢出的情况。
  • 内存限制:在某些情况下,即便设置了合适的堆栈大小和退出条件,由于系统的内存限制,也可能会出现栈内存溢出的情况。在这种情况下,可能需要优化程序的性能,减少内存的使用,或者增加系统的内存。

案例(局部变量过多、操作数栈深度过大影响栈内存的大小):

原代码:

public class Demo1 {
    public static void main(String[] args) {
        recursion();
    }
    public static int count = 0;
    public static void recursion() {
        System.out.println(++count);
        recursion();
    }
}
image.gif

运行结果:

image.gif

添加局部变量:

public class Demo1 {
    public static void main(String[] args) {
        recursion();
    }
    public static int count = 0;
    public static void recursion() {
        long x,y,z,i,j,k,a,b,c;
        System.out.println(++count);
        recursion();
    }
}
image.gif

运行结果:

image.gif


总结

JVM是Java程序的运行环境,负责字节码解释、内存管理、安全保障、多线程支持、性能监控和跨平台运行。本文主要介绍了栈内存溢出、设置虚拟机栈的大小等内容,希望对大家有所帮助。

相关文章
|
2天前
|
Java API 对象存储
JVM进阶调优系列(2)字节面试:JVM内存区域怎么划分,分别有什么用?
本文详细解析了JVM类加载过程的关键步骤,包括加载验证、准备、解析和初始化等阶段,并介绍了元数据区、程序计数器、虚拟机栈、堆内存及本地方法栈的作用。通过本文,读者可以深入了解JVM的工作原理,理解类加载器的类型及其机制,并掌握类加载过程中各阶段的具体操作。
|
7天前
|
存储 Java Linux
【JVM】JVM执行流程和内存区域划分
【JVM】JVM执行流程和内存区域划分
28 1
|
19天前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
77 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
1天前
|
算法 Java
JVM进阶调优系列(3)堆内存的对象什么时候被回收?
堆对象的生命周期是咋样的?什么时候被回收,回收前又如何流转?具体又是被如何回收?今天重点讲对象GC,看完这篇就全都明白了。
|
2月前
|
存储 程序员 编译器
堆和栈内存的区别是什么
【8月更文挑战第23天】堆和栈内存的区别是什么
141 4
|
2月前
|
消息中间件 设计模式 安全
多线程魔法:揭秘一个JVM中如何同时运行多个消费者
【8月更文挑战第22天】在Java虚拟机(JVM)中探索多消费者模式,此模式解耦生产与消费过程,提升系统性能。通过`ExecutorService`和`BlockingQueue`构建含2个生产者及4个消费者的系统,实现实时消息处理。多消费者模式虽增强处理能力,但也引入线程安全与资源竞争等挑战,需谨慎设计以确保高效稳定运行。
79 2
|
2月前
|
存储 监控 算法
深入解析JVM内部结构及GC机制的实战应用
深入解析JVM内部结构及GC机制的实战应用
|
2月前
|
Java Docker 索引
记录一次索引未建立、继而引发一系列的问题、包含索引创建失败、虚拟机中JVM虚拟机内存满的情况
这篇文章记录了作者在分布式微服务项目中遇到的一系列问题,起因是商品服务检索接口测试失败,原因是Elasticsearch索引未找到。文章详细描述了解决过程中遇到的几个关键问题:分词器的安装、Elasticsearch内存溢出的处理,以及最终成功创建`gulimall_product`索引的步骤。作者还分享了使用Postman测试接口的经历,并强调了问题解决过程中遇到的挑战和所花费的时间。
|
2天前
|
存储 缓存 算法
JVM核心知识点整理(内存模型),收藏再看!
JVM核心知识点整理(内存模型),收藏再看!
JVM核心知识点整理(内存模型),收藏再看!
|
8天前
|
存储 安全 Java
JVM锁的膨胀过程与锁内存变化解析
在Java虚拟机(JVM)中,锁机制是确保多线程环境下数据一致性和线程安全的重要手段。随着线程对共享资源的竞争程度不同,JVM中的锁会经历从低级到高级的膨胀过程,以适应不同的并发场景。本文将深入探讨JVM锁的膨胀过程,以及锁在内存中的变化。
15 1