本节笔者分享一个在实际工作中遇到的栈内存溢出(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. 利用 istio 来对运行在 Kubernetes 上的微服务进行管理

    尝试在一个准生产环境下,利用 istio 来对运行在 Kubernetes 上的微服务进行管理. 这一篇是第一篇,将一些主要的坑和环境准备工作. 内容较多,因此无法写成手把手教程,希望读者有一定 Ku ...

  2. 1.1 Introduction中 Topics and Logs官网剖析(博主推荐)

    不多说,直接上干货! 一切来源于官网 http://kafka.apache.org/documentation/ Topics and Logs 话题和日志 (Topic和Log) Let's fi ...

  3. 关于Promise的详细总结

    1. 异步回调 1.1 回调地狱 在需要多个操作的时候,会导致多个回调函数嵌套,导致代码不够直观,就是常说的回调地狱 1.2 并行结果 如果几个异步操作之间并没有前后顺序之分,但需要等多个异步操作都完 ...

  4. Dcloud课程7 单例模式一般用在什么场景

    Dcloud课程7 单例模式一般用在什么场景 一.总结 一句话总结:连接数据库,这样就保证了和数据之间只有一个连接,从而能够不占用多余资源,这样就极大的减少了资源浪费,减少了mysql或者说服务器压力 ...

  5. Day1:变量

    一.变量用来干嘛的 用来存东西的,方便后面调用 二.如何定义变量 name = "Hiuhung Wan" 变量名 = 值,一个等号是赋值号,右边的值赋值给左边 三.变量的一些用法 ...

  6. 【Codeforces Round #443 (Div. 2) A】Borya's Diagnosis

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 模拟 [代码] #include <bits/stdc++.h> using namespace std; const ...

  7. [Angular2 Router] Configure Auxiliary Routes in the Angular 2 Router - What is the Difference Towards a Primary Route?

    In this tutorial we are going to learn how we can can configure redirects in the angular 2 router co ...

  8. UVA 11090 - Going in Cycle!! SPFA

    http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&p ...

  9. 【POJ 3415】Common Substrings

    [链接]h在这里写链接 [题意]     求两个串的长度大于等于k的公共子串个数.     相同的重复计数. [题解]     先把两个字符串用一个分隔符分开.最好比出现的字符都大的一个数字.    ...

  10. Textillate.js有什么用及使用实例

    Textillate.js有什么用及使用实例 一.总结 一句话总结:Textillate.js – 实现动感的 CSS3 文本动画的简单插件 二.textillate.js 文字动画 textilla ...