zoukankan      html  css  js  c++  java
  • 栈内存溢出-StackOverflowError

    下面test方法是一个死循环自己调自己的例子。运行这个方法,你会看到熟悉又不常见的java.lang.StackOverflowError(栈内存溢出错误):

     1 package com.clz;
     2 
     3 import org.junit.Test;
     4 
     5 public class TestMain {
     6     @Test
     7     public void testStackOverflowError() {
     8         testStackOverflowError();
     9     }
    10 
    11 }

    运行结果:

    java.lang.StackOverflowError
        at com.clz.TestMain.testStackOverflowError(TestMain.java:8)--(第1条stackTrace)
        at com.clz.TestMain.testStackOverflowError(TestMain.java:8)--(第2条stackTrace)
        at com.clz.TestMain.testStackOverflowError(TestMain.java:8)--(第3条stackTrace)
        。。。。。。
        at com.clz.TestMain.testStackOverflowError(TestMain.java:8)--(第500条stackTrace)
        。。。。。。
        at com.clz.TestMain.testStackOverflowError(TestMain.java:8)--(第1022条stackTrace)
        at com.clz.TestMain.testStackOverflowError(TestMain.java:8)--(第1023条stackTrace)
        at com.clz.TestMain.testStackOverflowError(TestMain.java:8)--(第1024条stackTrace)

    注意到以上运行结果,一共会打印出来1024条stacktrace信息。说明java中线程栈的长度是1024。

    ------------我是么么哒分割线----------------

    线程栈:thread stack。我们分析程序的jvm dump时,往往需要通过查看Thread Stack来分析问题。比如下图:

    ------------我是么么哒分割线----------------

    java.lang.StackOverflowError栈内存溢出是进行复杂运算时非常容易出现的错误。

    我们来看如下代码的线程栈图示

    public class Demo {
        public static void main(String[] args) {
            String message="hello JVisualVM world";
            hello(message);
        }
    
        private static void hello(String text) {
            System.out.println(text);
        }
    }

    在java中,虚拟机会为每个任务的处理分配一个线程, 在这个线程里,每次调用一个方法,都会将本次方法调用的栈桢压入虚拟机栈里,这个栈桢里保存着方法内部的局部变量和其他信息。 不过呢,每个线程的虚拟机栈的大小是固定的,默认为1MB(上面的1024)。

    既然一个线程的虚拟机栈内存大小是有限的,那么假设不停的调用各种方法,对应的栈桢不停的压入栈中。当这些大量的栈桢消耗完毕这个1MB的线程栈内存,最终就会导致出现栈内存溢出的情况。

    ------------我是么么哒分割线----------------

    而我在上周四boss开工改版时,对redis分布式锁接口方法做了调整,却因为一个失误导致了StackOverflowError。

    >>先看接口定义,然后再说问题:

     1 package com.emax.zhenghe.common.concurrent.distributeRedisLock;
     2 
     3 public interface DistributedLock {
     4         
     5      boolean lock(String key);
     6     
     7      boolean lock(String key, int retryTimes);
     8     
     9      boolean lock(String key, int retryTimes, long sleepMillis);
    10     
    11      boolean lock(String key, long expireMillis);
    12     
    13      boolean lock(String key, long expireMillis, int retryTimes);
    14     
    15      boolean lock(String key, long expireMillis, int retryTimes, long sleepMillis);
    16     
    17      boolean releaseLock(String key);
    18 }

    >>接下来说问题:

    分布式锁在技术层面有两种应用场景:
    1. 可以保证幂等性(防重与幂等有区别:幂等通常是对并发请求的防重控制;防重除了需要分布式保证幂等以外,还需要做数据防重校验,因为重复请求可能不是并发请求过来的,有可能是隔了很长时间的重复数据提交,就是用DCL)
    2. 实现进程同步(类似于线程synchronized锁):当锁存在时,需要不断尝试重试取锁,实现自旋等待。

    这个接口正好也为两种应用场景定义了方法API。问题在于,我们看这些lock重载方法,比较第7行的lock(String,int)与第11行的lock(String,long),再比较第9行的lock(String,int,long)与第13行的lock(String,long,int),太容易误用了。事实也证明了这一点,项目的业务代码里有很多用来保证幂等性的逻辑调用的是lock(String,int)或lock(String,long,int),而非lock(String,long),那么,显然无法达到幂等控制的效果。

    为了解决项目中现存的这种误用,并规避日后的误用,有必要重构这个接口。如下是第一版:

     1 package com.emax.zhenghe.common.concurrent.distributeRedisLock;
     2 
     3 public interface DistributedLock {
     4     boolean lock(String key);
     5 
     6 //     boolean lock(String key, int retryTimes);
     7 
     8 //    boolean lock(String key, int retryTimes, long sleepMillis);
     9 //     boolean synchronize(String key, int retryTimes, long sleepMillis);
    10 
    11     boolean lock(String key, long expireMillis);
    12 
    13     /**
    14      * 注:有很多项目很多代码调用了这个方法,过渡阶段先保留这个方法api
    15      * 注:此方法不再使用。请不要使用过期的方法
    16      * @param key
    17      * @param expireMillis
    18      * @param useless 有很多地方在用,不得不定义这个寂寞参数
    19      * @return
    20      */
    21     @Deprecated
    22     default boolean lock(String key, long expireMillis,int useless){
    23         return lock(key, expireMillis, 0);
    24     }
    25 
    26     //    boolean lock(String key, long expireMillis, int retryTimes);
    27     boolean synchronize(String key, long expireMillis, int retryTimes);
    28 
    29     //    boolean lock(String key, long expireMillis, int retryTimes, long sleepMillis);
    30     boolean synchronize(String key, long expireMillis, int retryTimes, long sleepMillis);
    31 
    32     boolean releaseLock(String key);
    33 
    34     boolean releaseLockUnsafe(String key);
    35 }

    导致出现StackOverflowError的,正是第22行标记了过时的lock(String key, long expireMillis,int useless)。这个方法调用的是其自身!而我的本意是要它调用第11行的lock(key, expireMillis):

    。。。
    13     /**
    14      * 注:有很多项目很多代码调用了这个方法,过渡阶段先保留这个方法api
    15      * 注:此方法不再使用。请不要使用过期的方法
    16      * @param key
    17      * @param expireMillis
    18      * @param useless 有很多地方在用,不得不定义这个寂寞参数
    19      * @return
    20      */
    21     @Deprecated
    22     default boolean lock(String key, long expireMillis,int useless){
    23         return lock(key, expireMillis);
    24     }
    。。。
  • 相关阅读:
    使用 PyCharm 远程调试 Django 项目
    (坑集)Python环境配置
    字典的使用
    列表的使用
    字符串的魔法
    php 文件函数
    php 时间函数
    php xajax库基本知识
    php header函数
    c++注释
  • 原文地址:https://www.cnblogs.com/buguge/p/14832996.html
Copyright © 2011-2022 走看看