本节笔者分享一个在实际工作中遇到的栈内存溢出(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. 【2017 Multi-University Training Contest - Team 7】Just do it

    [Link]:http://acm.hdu.edu.cn/showproblem.php?pid=6129 [Description] 设定b[i]=a[1]^a[2]^a[3]^------a[i] ...

  2. 洛谷 P2677 超级书架 2

    P2677 超级书架 2 题目描述 Farmer John最近为奶牛们的图书馆添置了一个巨大的书架,尽管它是如此的大,但它还是几乎瞬间就被各种各样的书塞满了.现在,只有书架的顶上还留有一点空间. 所有 ...

  3. Core Animation 文档翻译—附录B(可动画的属性)

    前言   许多CALayer和CIFliter的属性都是可动画的.本节附录列出了这些属性默认使用的动画.   CALayer可动画属性   表B-1展示了CALayer类的可动画属性.针对每个属性此表 ...

  4. HTML基础-第一讲

    转自:https://blog.csdn.net/likaier/article/details/326639?utm_source=blogxgwz9 HTML是网页主要的组成部分,基本上一个网页都 ...

  5. 94.文件bat脚本自删除

    taskkill / f / im 自删除.exedel 自删除.exedel 1.bat void main() { FILE *pf = fopen("1.bat", &quo ...

  6. 如何把excel同一个单元格内的文字和数字分别提取出来?

    平台:excel 2010 目的:把excel同一个单元格内的文字和数字分别提取出来 操作: 假设数据在A1单元格:如果文字在前,B1=left(A1,lenb(A1)-len(A1))可得文字,C1 ...

  7. jquery中prop()和attr()的使用

    jquery1.6+出现的prop()方法. • 对于HTML元素本身就带有的固有属性,在处理时,使用prop方法. • 对于HTML元素我们自己自定义的DOM属性,在处理时,使用attr方法. • ...

  8. CODEVS——T 1993 草地排水 USACO

    http://codevs.cn/problem/1993/  时间限制: 2 s  空间限制: 256000 KB  题目等级 : 钻石 Diamond 题解  查看运行结果     题目描述 De ...

  9. 语言模型(Language Modeling)与统计语言模型

    1. n-grams 统计语言模型研究的是一个单词序列出现的概率分布(probability distribution).例如对于英语,全体英文单词构成整个状态空间(state space). 边缘概 ...

  10. 11.3 Android显示系统框架_最简单的surface测试程序

    APP有一个surface(界面),其有多个buffer用来存放界面数据,这些buffer是向surfaceflinger申请的: 因此我们编写的surface测试程序步骤: (1)获得surface ...