本节笔者分享一个在实际工作中遇到的栈内存溢出(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. 我的第一个JS组件-跨浏览器JS调试工具

    武汉九天鸟-p2p网贷系统开发-互联网应用软件开发 公司官网:http://jiutianniao.com  社交问答:http://ask.jiutianniao.com 最近,在看公司一个JS大牛 ...

  2. hibernate中的事务管理是怎么概念?

    1.JDBC事务 JDBC 事务是用 Connection 对象控制的.JDBC Connection 接口( java.sql.Connection )提供了两种事务模式:自动提交和手工提交. ja ...

  3. 使用JOTM实现分布式事务管理(多数据源)

    使用spring和hibernate可以很方便的实现一个数据源的事务管理,但是如果需要同时对多个数据源进行事务控制,并且不想使用重量级容器提供的机制的话,可以使用JOTM达到目的. JOTM的配置十分 ...

  4. install-软件安装跟push的区别

    今天在做项目的时候,需要往一个user版本的手机中安装一个应用,就在网上查了相应的方法,可以使用如下命令 adb install -r out/target/product/vanzo6752_lwt ...

  5. 89.hash算法实现CSDN密码处理

    初始化,数据的行数,hash链表结构体,存储头结点 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdl ...

  6. Redis-消费模式

    一 . 两种模式简介 发布消息通常有两种模式:队列模式(queuing)和发布订阅模式(qublish-subscribe).队列模式中,consumers可以同时从服务端读取消息,每个消息纸杯其中一 ...

  7. 1.8 Python基础知识 - 数值类型

    一.int类型(任意精度整数) 整型类型(int)是表示整数的数据类型.与其他计算机语言有精度限制不同,Python的整数位数可以为任意长度位数(只受限制于计算机内存) 数字字符串即整型常量. pyt ...

  8. linux网站发布操作流程

    Linux 添加用户命令: useradd bm -g webTemp http://www.runoob.com/linux/linux-vim.html Linux关于网站发布操作流程 虚拟机地下 ...

  9. [D3] Animate Chart Axis Transitions in D3 v4

    When the data being rendered by a chart changes, sometimes it necessitates a change to the scales an ...

  10. python课程:python3的数字与字符串

    一下是基于python2的教程的 python中有 多个数据类型,和,两种字符串类型 他们都是不可变的.