本节笔者分享一个在实际工作中遇到的栈内存溢出(StackOverflowError)问题,以及其解决方案。

问题介绍:笔者负责的一个Java Web项目在启动的时候,需要有一些初始化操作,而接下来的代码的执行必须要等到相关初始化操作完成。为了实现这个等待的功能,这个项目之前的负责人使用了一个递归方法进行判断,最终导致了应用每次在启动的时候,都会出现StackOverFlowException异常。

首先我们介绍,为什么递归操作会引起栈内存溢出?

线程在执行的时候,会直接调用一系列的方法,而被直接调用的方法可能又调用了其他的方法。为了保证直接或者间接被调用的方法可以按照方法声明的顺序那样进行执行,每个线程都在栈内存中维护了一个数据结构,就是一个栈。一个线程在执行的时候,每当遇到一个方法的开始,就将这个方法的相关信息(称之为栈帧)压入栈,而方法结束后再进行弹栈。通过这种方式保证了方法执行顺序的正确性。

但是因为栈内存的大小是有限制的,默认请下一般是1M。当我们再使用递归方法的时候,方法会存在不断的循环调用,因此会不断的往栈中压入数据,当数据量超过1M的时候,就会出现栈内存溢出。

为了说明上面这个问题,请看以下的代码演示:

  1. public class StackOverFlowDemo {
  2. int count=0;
  3. public void recursiveMethod(){
  4. if (count==1000000){//递归方法执行1000000次时,结束
  5. return;
  6. }
  7. count++;
  8. System.out.println("执行了:"+count+"次");
  9. recursiveMethod();//递归调用
  10. }
  11. public static void main(String[] args) {
  12. StackOverFlowDemo stackOverFlowDemo = new StackOverFlowDemo();
  13. stackOverFlowDemo.recursiveMethod();
  14. System.out.println("执行其他代码...");
  15. }
  16. }

在这段代码中,我们定义了一个递归方法recursiveMethod(),我们希望递归方法执行了1百万次之后结束,为了便于观察,每次递归都打印出当前是第几次迭代。但是真的可以递归1百万次吗?以下是其中一次运行结果:

执行了:3978次

执行了:3979次

执行了:3980次

执行了:3981次

Exception in thread "main" java.lang.StackOverflowError

at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)

at sun.nio.cs.UTF_8.access$200(UTF_8.java:57)

at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:636)

at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:691)

at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:579)

at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:271)

at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)

可以看到,事实上递归了3981次的后,就抛出了StackOverflowError异常。这提示我们,在Java开发过程中,要慎用递归,尤其是在不能预估递归方法大概需要执行多少次的时候,最好就不好使用。

为了解决上述问题,我们需要进行一些改造。

  1. public class SpinLockDemo {
  2. int count=0;
  3. public void incr(){
  4. count++;
  5. System.out.println("执行了:"+count+"次");
  6. }
  7. public static void main(String[] args) {
  8. SpinLockDemo spinLockDemo = new SpinLockDemo();
  9. while(spinLockDemo.count!=1000000){//这段代码其实就是一个自旋锁
  10. spinLockDemo.incr();
  11. }
  12. System.out.println("执行其他代码...");
  13. }
  14. }

因为我们只是希望count变量值达到1百万的时候,才继续执行剩余部分的代码。所以我们可以将判断条件放入一个while循环中,只要没到1000000次,就继续增加。到达之后,循环结束,执行剩余部分的代码。这里的while循环,其实就是所谓的自旋锁(Spin Lock)。需要注意的是:自旋锁不是真正的锁,其只是解决思路的一种方式,只要不能继续往下执行,就不断的循环。

原文地址:http://www.tianshouzhi.com/api/tutorials/mutithread/289

自旋锁解决StackOverflowError案例的更多相关文章

  1. 鸿蒙内核源码分析(互斥锁篇) | 比自旋锁丰满的互斥锁 | 百篇博客分析OpenHarmony源码 | v27.02

    百篇博客系列篇.本篇为: v27.xx 鸿蒙内核源码分析(互斥锁篇) | 比自旋锁丰满的互斥锁 | 51.c.h .o 进程通讯相关篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁当立贞 ...

  2. JUC 并发编程--05, Volatile关键字特性: 可见性, 不保证原子性,禁止指令重排, 代码证明过程. CAS了解么 , ABA怎么解决, 手写自旋锁和死锁

    问: 了解volatile关键字么? 答: 他是java 的关键字, 保证可见性, 不保证原子性, 禁止指令重排 问: 你说的这三个特性, 能写代码证明么? 答: .... 问: 听说过 CAS么 他 ...

  3. 可重入锁 公平锁 读写锁、CLH队列、CLH队列锁、自旋锁、排队自旋锁、MCS锁、CLH锁

    1.可重入锁 如果锁具备可重入性,则称作为可重入锁. ========================================== (转)可重入和不可重入 2011-10-04 21:38 这 ...

  4. Java锁之自旋锁详解

    锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) .这些已经写好提供的锁为我们开发提供了便利,但是锁的具体性质以及类 ...

  5. 可重入锁 & 自旋锁 & Java里的AtomicReference和CAS操作 & Linux mutex不可重入

    之前还是写过蛮多的关于锁的文章的: http://www.cnblogs.com/charlesblc/p/5994162.html <[转载]Java中的锁机制 synchronized &a ...

  6. SpinLock 自旋锁, CAS操作(Compare & Set) ABA Problem

    SpinLock 自旋锁 spinlock 用于CPU同步, 它的实现是基于CPU锁定数据总线的指令. 当某个CPU锁住数据总线后, 它读一个内存单元(spinlock_t)来判断这个spinlock ...

  7. 自旋锁spin_lock和raw_spin_lock

    自旋锁spin_lock和raw_spin_lock Linux内核spin_lock.spin_lock_irq 和 spin_lock_irqsave 分析 http://blog.csdn.ne ...

  8. linux内核--自旋锁的理解

    http://blog.chinaunix.net/uid-20543672-id-3252604.html 自旋锁:如果内核配置为SMP系统,自旋锁就按SMP系统上的要求来实现真正的自旋等待,但是对 ...

  9. Nginx学习之四-Nginx进程同步方式-自旋锁(spinlock)

    自旋锁简介 Nginx框架使用了三种消息传递方式:共享内存.套接字.信号. Nginx主要使用了三种同步方式:原子操作.信号量.文件锁. 基于原子操作,nginx实现了一个自旋锁.自旋锁是一种非睡眠锁 ...

随机推荐

  1. unity-unet-同步各个player唯一标识

    Multiplayer Game 中所有 player 都有一个唯一标识.在unet中可以通过 Network Identity 组件获取到该 player 在整个网络整的 唯一 的连接 id 这里测 ...

  2. 【例题 8-3 UVA - 1152】4 Values whose Sum is 0

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 显然中间相遇. 自己写了个hash处理一下冲突就可以了. [代码] /* 1.Shoud it use long long ? 2. ...

  3. 又一次认识java(一) ---- 万物皆对象

    假设你现实中没有对象.至少你在java世界里会有茫茫多的对象,听起来是不是非常激动呢? 对象,引用,类与现实世界 现实世界里有许很多多的生物,非生物,跑的跳的飞的,过去的如今的未来的,令人眼花缭乱.我 ...

  4. 一次Linux磁盘损坏导致系统不可用恢复实例

    Linux操作系统的server重新启动后.系统启动报错,系统无法正常使用. 1.报错信息 1.1.报错屏幕信息 1.2.报错信息提取关键信息 (1)/dev/sda3:File -(inode #1 ...

  5. HTML基础第四讲---图像

    转自:https://blog.csdn.net/likaier/article/details/326735 图像,也就是images,在html语法中用img来表示,其基本的语法是:   < ...

  6. 洛谷—— P1069 细胞分裂

    https://www.luogu.org/problem/show?pid=1069#sub 题目描述 Hanks 博士是 BT (Bio-Tech,生物技术) 领域的知名专家.现在,他正在为一个细 ...

  7. Windows Forms 对话框篇

    1,标准对话框 Windows内置的对话框,又叫公用对话框,它们作为组件提供的,并且存在于System.Windows.Forms命名空间中. 手工方式: private void button1_C ...

  8. 软件——keil的查找,错误,不能跳转到相应的行

    为什么MDK  keil4.7双击搜索结果不能跳转到相应位置 KEIL搜索的时候双击不跳转到相应的位置 为什么keil点击不能跳转到错误处的问题 在keil中,双击Find In Files中某一行, ...

  9. javascript中的事件问题的总结

    一.什么是事件? 事件就是DOM和浏览器之间的交互行为(只要触发了这个行为,也就相当于触发了事件),我们可以通过事件监听来绑定事件,例如:box.onclick=function(){},如果我们点击 ...

  10. C#游戏开发高速入门 2.1 构建游戏场景

    C#游戏开发高速入门 2.1  构建游戏场景 假设已经计划好了要编写什么样的游戏,在打开Unity以后.要做的第一件事情就是构建游戏场景(Scene).游戏场景就是玩家游戏时,在游戏视图中看到的一切. ...