一文优化java.lang.StackOverflowError的堆栈溢出问题及递归引发的java.lang.StackOverflowError错误

news2024/10/2 6:35:26

文章目录

  • 1. 问题引出
  • 2. 分析问题
    • 2.1 为什么递归调用会导致堆栈溢出
    • 2.2 数组太大或分配内存多于可用内存导致堆栈异常
  • 3. 优化避免栈溢出
    • 3.1 尾递归优化
    • 3.2 循环替代递归
  • 4. 重要总结

1. 问题引出

今天在编码时,出现了java.lang.StackOverflowError,就感觉很莫名其妙。

由于源代码涉及到公司业务,暂无法公开到博客上,望读者见谅。

但为了复现StackOverflowError的错误,我特地编写如下代码来模拟:

/**
 * 使用junit注解来调用testDegression方法
 *
 * @author super先生
 * @datetime 2023/2/15 20:41
 */
@Test
public void testStackOverFlow() {
    testDegression(100000);
}

/**
 * 使用递归来模拟StackOverflowError
 * 栈帧不断增加累积,栈的深度直到超出JVM所允许的深度
 *
 * @param param 整数原始值
 * @author super先生
 * @datetime 2023/2/15 20:40
 */
private void testDegression(int param) {
    param--;
    //递归,结束条件
    if (param == 0) {
        return;
    }
    testDegression(param);
}

该代码报出的如下图错误:

在这里插入图片描述

由上图可以看到,testDegression一直在调用自己,也就是递归,从而导致栈溢出。

为什么递归调用会报出这个错误(java.lang.StackOverflowError)呢?

2. 分析问题

此时,正赶上ChatGPT,我们不妨让ChatGPT解释这个错误(java.lang.StackOverflowError),解释结果如下图所示:

在这里插入图片描述

java.lang.StackOverflowError

StackOverflowError is a type of error that occurs when a program performs too many recursive calls, leading to an overflow of the call stack. This type of error is most commonly seen when a programming language does not terminate a recursive call, or when a programmer creates an infinite loop or recursive call. StackOverflowError can also be caused by an array being too large or an application attempting to allocate more memory than is available.

芭比Q了,解释结果是一段英文,不妨使用谷歌将其翻译成中文,如下所示:

java.lang.StackOverflowError

stackoverflowerror是一种错误,当程序执行太多递归调用时,会导致堆栈的溢出。当编程语言未终止递归调用,或者程序员创建无限循环或递归调用时,这种错误最常见。

stackoverflowerror也可能是由于数组太大或试图分配比可用的更多内存的应用程序引起的。

根据中文翻译可以知道,产生堆栈溢出(StackOverflowError)的原因有两个:

  1. 递归调用,这是最常见的错误

  2. 数组太大或分配内存比可用内存多

2.1 为什么递归调用会导致堆栈溢出

在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当程序执行进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。

由于栈的大小不是无限的,所以,递归调用的次数过多,就会导致栈溢出。

函数的参数是通过stack栈来传递的,在调用中会占用线程的栈资源。

递归调用在到达最后的结束点后,函数才能依次退栈清栈,如果递归调用层数过多,就可能导致占用的栈资源超过线程的最大值,从而导致栈溢出,程序异常退出。

2.2 数组太大或分配内存多于可用内存导致堆栈异常

存到栈上的主要内容是:

  1. 局部变量

  2. 函数调用的函数环境,包括函数参数等。

假设局部变量buff1M,如下代码所示:

char buff[1024*1024]

而你的栈默认也是1M大小,这就会发生栈溢出。

因为其他东西会占掉少量栈空间,局部变量能用的空间肯定小于1M,程序在执行到main函数之前,就会跳出stack overflow异常。

3. 优化避免栈溢出

当栈不够使用时,一种办法是修改程序:

主要还是要注意递归调用引起的栈溢出,多数情况可以通过算法优化来解决:

  1. 控制递归深度。例如,使用动态规划来代替递归算法等。

  2. 修改栈的大小。

3.1 尾递归优化

尾递归是指在函数返回的时候,调用函数本身,并且return语句不能包含表达式。

如果递归调用,都出现在函数的末尾,这个递归函数就是尾递归的函数。

尾递归函数的特点是在回归过程中,不用做操作,这个特性很重要,因为大多数现代编译器会利用这一特点,自动生成优化的代码。

有些语言极力提倡尾递归,因为它们的编译器会对代码进行优化,不会因为递归次数的增加,给函数栈带来巨大的开销。

尾递归优化,使无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

递归的优点是逻辑清晰。

3.2 循环替代递归

所有的递归都可以改写成循环的方式。

斐波那契数列: 1,1,2,3,5,…

求斐波那契数列的第N项的值

尾递归和循环的执行效率都非常高。

但是尾递归的递归层数大到一定程度会出现段错误。

尾递归的函数栈开销比普通递归要小的多,执行效率大很多。

但是尾递归仍然有函数栈开销。

正因为尾递归具有函数栈开销,其调用次数比循环小很多。

使用如下三种方式(一般递归,尾递归,循环取代递归),来实现实现一个斐波那契数列,能不用递归就别用递归,能用尾递归就用尾递归。

  1. 使用一般递归
int fib_normal(int n)
{
    if (n <= 2)
        return 1;
    else
        return fib_normal(n-1) + fib_normal(n-2);
}
  1. 尾递归
int fib_rail(int n, int first,  int second)
{
    if (n == 1) return first;
    if (n == 2) return second;
    return fib_rail(n-1, second, second+first);
}
unsigned int fib_rail_rec(unsigned int n)
{
    return fib_rail_rec(n, 1, 1);
}
  1. 循环取代递归
int fib_no(int n)
{
    if (n <= 2)
        return 1;
    int x=1, y=1, y_tmp=0;
    for (int i=0; i<n-2; i++)
    {
        y_tmp = y;
        y = x+y;
        x = y_tmp;
    }
    return y;
}

4. 重要总结

之前喜欢使用谷歌翻译报错的信息,自从出现了ChatGPT,便喜欢使用ChatGPT来翻译。

如果你想了解什么是ChatGPT以及它的用法,可以点击如下链接:

  1. 全网最详细的介绍ChatGPT

  2. ChatGPT、低代码等技术出现会不会导致底层程序员失业

  3. 全网推荐7款github上有趣的ChatGPT的应用源码

  4. 如何调用ChatGPT的API接口到官方例子的说明以及GitHub上的源码应用

  5. 全网详细解读基于java调用ChatGPT的API接口

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/348091.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python小游戏】没点儿技术真不敢这么玩儿:人工智能挑战《贪吃蛇》,来自AI的艺术——超级游戏高手世界最高纪录秒被盘?

前言 每天分享各种Python项目、好玩的Pygame游戏、Python的爬虫、数据分析案例、有趣的人 工智能知识等。期待你的关注哦&#xff01; 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末公众hao即可免费。 哈喽&…

探索IP地址的应用

无论是互联网行业还是传统行业都会用到网络&#xff0c;作为企业如何维护网络安全&#xff0c;保障网站不被攻击&#xff0c;数据不被泄露等。这个时候我们就会通查询IP归属地&#xff0c;辅助企业解决安全问题。下面介绍一下ip归属地在各行业的具体应用。1.网安行业应用一&…

SpringBoot实现登录拦截器超详细(springboot拦截器excludePathPatterns方法不生效的坑)

文章目录SpringBoot实现登录拦截器1、SpringBoot 实现登录拦截的原理1.1、实现HandlerInterceptor接口1.2、实现WebMvcConfigurer接口&#xff0c;注册拦截器1.3、保持登录状态springboot拦截器excludePathPatterns方法不生效的坑与解决方法一、前言二、问题三、解决方法四、总…

C语言编译过程

C语言编译过程1、C语言编译过程2、单c文件编译实践3、多c文件编译实践4、define4.1、不带参宏4.2、带参宏4.3、带参宏和带参函数的区别5、选择性编译ifdef、ifndef、if5.1、#ifdef5.2、#ifndef5.3、#if6、静态库和动态链接库6.1、静态库实践6.1.1、将mylib.c制作成静态库6.1.2、…

Baklib知识库管理平台,协助组织提升知识管理水平

随着信息时代和知识经济时代的到来&#xff0c;企业内部信息资料繁多冗杂&#xff0c;知识管理逐渐成为各大企业的重要工作之一&#xff0c;企业管理者无不感受到巨大的压力&#xff0c;怎么样将知识进行有效的管理&#xff0c;成为一个难点&#xff0c;并且随着信息不断的更迭…

Java企业级信息系统开发学习笔记(1)初探Spring与骑士傻龙实例

该文章主要为完成实训任务&#xff0c;详细实现过程及结果见【http://t.csdn.cn/iK3aM】 文章目录1. 创建Maven项目2. 添加Spring依赖3. 创建杀龙任务4. 创建勇士类5. 采用传统的方式6. 采用Spring容器让勇敢骑士完成杀龙任务6.1 创建日志属性文件6.2 创建Spring配置文件6.3 在…

CobaltStrike上线微信通知

CobaltStrike上线微信通知 利用pushplus公众号&#xff08;每天免费发送200条消息&#xff09; http://www.pushplus.plus/push1.html 扫码登录后需要复制token 可以测试一下发送一下消息&#xff0c;手机会受到如下消息。可以在微信提示里将消息免打扰关闭&#xff08;默认…

分布式光伏储能系统的优化配置方法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

期末复习提纲

复习提纲 题型&#xff1a;编程题3题&#xff0c;综合题4题。 一、编程题&#xff1a; 1、链表的类型定义&#xff1b;邻接矩阵表示图的类型定义&#xff1b;链接表表示图的类型定义&#xff1b;vector数组表示图的定义和使用方法。 2、链表中结点的插入和删除操作&#xff…

linux高级命令之编辑器 vim

编辑器 vim学习目标能够说出vim的三种工作模式能够说出vim对应复制和粘贴命令1. vim 的介绍vim 是一款功能强大的文本编辑器&#xff0c;也是早年 Vi 编辑器的加强版&#xff0c;它的最大特色就是使用命令进行编辑&#xff0c;完全脱离了鼠标的操作。2. vim 的工作模式命令模式…

2023想转行软件测试的看过来,你想要了解的薪资、前景、岗位方向、学习路线都讲明白了

在过去的一年中&#xff0c;软件测试行业发展迅速&#xff0c;随着数字化技术应用的广泛普及&#xff0c;业界对于软件测试的要求也在持续迭代与增加。 同样的&#xff0c;有市场就有需求&#xff0c;软件测试逐渐成为企业中不可或缺的岗位&#xff0c;作为一个高薪又需求广的…

怎么解密MD5,常见的MD5解密方法,一看就会

MD5是一种被广泛使用的密码散列函数&#xff0c;曾在计算机安全领域使用很广泛&#xff0c;但是也因为它容易发生碰撞&#xff0c;而被人们认为不安全。那么&#xff0c;MD5应用场景有哪些&#xff0c;我们怎么解密MD5&#xff0c;本文将带大家了解MD5的相关知识&#xff0c;以…

Laravel创建定时任务

创建一个任务&#xff0c;创建成功后会在App/Console/Commands中生成一个以Test命名的文件&#xff0c;我们可以在这里面写我们的任务指令。 php artisan make:command Test 运行这个定时任务 run 是运行一次&#xff0c;我们可以用来测试是否成功&#xff0c;work是一直运行&a…

Jenkins的使用教程

介绍&#xff1a; Jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件的持续集成变成可能。 目的: 最重要目的就是把原来分散在各个机器上繁杂的工作全部…

hadoop高可用搭建

修改计算机名称 //修改计算机名称 [rootlocalhost ~]# hostnamectl set-hostname ant150//快速生效 [rootlocalhost ~]# bash 主机名称映射 [rootant150 ~]# vim /etc/hosts 192.168.153.150 ant150 192.168.153.151 ant151 192.168.153.152 ant152 192.168.153.153 ant153 …

数据分析-深度学习 Tensorflow Day6

我们需要解决的问题&#xff1a;1&#xff1a; 什么是bp 神经网络&#xff1f;2&#xff1a;理解bp神经网络需要哪些数学知识&#xff1f;3&#xff1a;梯度下降的原理4: 激活函数5&#xff1a;bp的推导。1.什么是bp网络&#xff1f;引用百度知道回复&#xff1a;“我们最常用的…

【mmrotate】旋转目标检测之训练DOTA数据集

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 mmrotate训练DOTA数据集记录 1. 正文 1.1 数据准备 数据介绍部分&#xff0c;参考DOTA数据介绍&#xff0c;官方提供了裁剪工具development kit。这里…

LINUX内核链表

LINUX内核链表 一、传统链表的缺陷 传统的双向循环链表概念简单&#xff0c;操作方便&#xff0c;但存在有致命的缺陷&#xff0c;用一句话来概括就是&#xff1a; 每一条链表都是特殊的&#xff0c;不具有通用性。换句话说&#xff0c;对于每一种不同的数据&#xff0c;所构…

java对象克隆和面向对象的设计原则

java进阶注解内置注解元注解自定义注解对象克隆浅克隆深克隆java设计模式建模语言类之间的关系依赖关系关联关系单向关联双向关联自关联聚合关系组合关系继承关系实现关系面向对象设计原则单一职责开闭原则里氏替换原则依赖倒置接口隔离迪米特原则组合/聚合复用原则注解 java注…

关于ucharts在小程序中的使用

项目添加组件 画图之前&#xff0c;首先需要引入ucharts组件&#xff0c;引入链接https://ext.dcloud.net.cn/plugin?id271。 点击下图中红色方框内容&#xff1a; 导入完成后&#xff0c;与uni其他组件一样&#xff0c;无需引入&#xff0c;直接使用即可。 使用组件 uchar…