栈溢出

栈溢出

 

做一个手机项目,在手机上运行死机,trace出来报错 “栈溢出”,首先修改了函数中较大的数组,超过128的基本上都去掉了,还是挂机, 找了半天,感觉无处下手, 最后测试出一种情况,不挂机,就是 不加载所有的图片,正常了,最后的解决办法是:在一开始进入程序时,不加载所有的图片 ,等程序初始化好了,再开始加载图片,不挂机了 。

 

 

下面是转载别人的一篇文章

 

最近在做一个程序(VC6.0),功能大概有网络通信、数据库、绘图等。测试的时候程序一运行到某个函数就出现此错误,查了很多地方,试了很多解决办法,终于把问题解决了,写个日志提醒一下自己,也希望作为一个普遍解决办法让大家少费工夫(其他编译器也会出现同样的问题)。
    大家都知道,Windows程序的内存机制大概是这样的,全局变量(局部的静态变量本质也属于此范围)存储于堆内存,该段内存较大,一般不会溢出;函数地址、函数参数、局部变量等信息存储于栈内存,VC6中栈内存默认大小为1M,对于当前日益扩大的程序规模而言,稍有不慎就可能出问题。
(动态申请的内存即new出来的内存不在栈中)
    即如果函数这样写:
    void test_stack_overflow()
    {
       char* chdata = new[2*1024*1024];
       delete []chdata;
    }
    是不会出现这个错误的,而这样写则不行:
    void test_stack_overflow()
    {
       char chdata[2*1024*1024];
    }
    大多数情况下都会出现内存溢出的错误,不信在vc6中随便做个程序,调用一下这个函数试式。


    出现栈内存溢出的常见原因有2个:
    1> 函数调用层次过深,每调用一次,函数的参数、局部变量等信息就压一次栈。
    2> 局部静态变量体积太大
    第一种情况不太常见,因为很多情况下我们都用其他方法来代替递归调用(反正我是这么做的),所以只要不出现无限制的调用都应该是没有问题的,起码深度几十层我想是没问题的,这个我没试过但我想没有谁会把调用深度作那么多。检查是否是此原因的方法为,在引起溢出的那个函数处设一个断点,然后执行程序使其停在断点处, 然后按下快捷键Alt+7调出call stack窗口,在窗口中可以看到函数调用的层次关系。

    第二种情况比较常见了,我就是犯了这个错误,我在函数里定义了一个局部变量,是一个类对象,该类中有一个大数组,大概是1.5M。
    
    解决办法大致说来也有两种:
    1> 增加栈内存的数目
    2> 使用堆内存
    增加栈内存方法如下,在vc6种依次选择Project->Setting->Link,在Category中选择output,在Reserve中输入16进制的栈内存大小如:0x10000000,然后点ok就可以了。
  
    其他编译器也有类似的设置,个人认为这不是一个好办法,有一个致命原因,不知道有没有人遇到过,我把栈内存改大后,与数据库建立不了连接了(ADO方式,Acess数据库),把栈内存还原,问题立刻消失。不知道究竟是什么原因,有知道的可以告诉我。
     email: la_ariza@sina.com

     第二种解决办法是比较可行的,具体实现由很多种方法可以直接把数组定义改成指针,然后动态申请内存;也可以把局部变量变成全局变量,一个偷懒的办法是直接在定义前边加个static,呵呵,直接变成静态变量(实质就是全局变量)。即可以把上例中的函数这么写:

    void test_stack_overflow()
    {
       static char chdata[2*1024*1024];
    }


    当然,除非万不得已,尽量不要使用这么大的数组,出现这种情况多半说明程序结构有问题。

<think>我们之前讨论的是在流程回退功能中,当处于HR人事节点时,回退节点列表为空的问题。我们进行了多次修正,包括历史节点获取、类型转换和模型获取方式等。 现在用户报告“栈溢出了”,这通常发生在递归函数没有正确终止条件时。回顾我们之前的代码,在`isUpstreamNode`方法中使用了循环来向上游回溯,但用户提到栈溢出,可能是递归实现导致的?然而我们之前实现的是循环而非递归。 检查最后提供的代码: 在`isUpstreamNode`方法中,我们使用了while循环来向上游回溯,并没有使用递归。因此栈溢出可能发生在其他部分。 但注意到用户最后提供的代码片段是历史活动查询的流式处理,并没有明显的递归。因此需要重新检查整个流程回退功能中可能存在的递归调用。 可能的递归点: 1. 在`findReturnTaskList`方法中,我们调用了`getFinishedUserTasks`和`isValidReturnNode`,而`isValidReturnNode`又调用了`isUpstreamNode`,但这些都是普通方法,没有递归。 2. 另一个可能性:在流程回退操作本身(用户未提供代码)可能递归调用了某些服务。 然而,用户明确指出“栈溢出了”,我们需要定位到具体的栈溢出错误。 根据上下文,最可能发生栈溢出的地方是我们实现的`isUpstreamNode`方法中的循环逻辑吗?实际上,循环不会导致栈溢出,递归才会。 重新审视代码,发现一个潜在问题:在`isUpstreamNode`的循环中,我们遍历上游节点,但是当流程有循环结构(比如循环网关)时,可能会陷入无限循环,从而导致栈溢出?不,循环是迭代循环,不会增加栈深度,但会无限循环(表现为CPU100%),而不是栈溢出。 因此,栈溢出一定是由于其他地方出现了递归调用且没有正确终止。 考虑之前代码中是否有递归?在最初版本的`isValidReturnNode`方法中,我们并没有递归。但是,在用户提供的代码片段中,我们并没有看到完整的流程回退操作,可能回退操作本身包含了递归。 由于用户没有提供完整的回退操作代码,我们只能假设在流程回退的执行部分存在递归调用。 但是,用户是在HR人事节点调用`findReturnTaskList`方法时出现栈溢出,而该方法我们已经修改为使用循环回溯,因此问题可能不在这个方法。 另一种可能性:在获取BPMN模型时,Camunda内部实现使用了递归,而我们的模型存在问题(比如循环引用)?这种可能性较小。 因此,我们需要用户提供具体的栈溢出异常堆栈信息,以定位问题。 然而,根据问题描述,我们可以推测一个常见原因:在`isUpstreamNode`方法中,如果流程存在环(比如通过网关形成的环),则循环无法退出,但这是无限循环(迭代)问题,不是栈溢出栈溢出一定是递归调用过深。 所以,可能是在其他地方存在递归。比如,在流程回退的执行代码中,可能递归地调用了`findReturnTaskList`方法?或者在其他服务中。 考虑到用户没有提供完整的回退执行代码,我们只能就已知代码进行分析。 在已知代码中,没有明显的递归调用。因此,建议用户检查栈溢出异常的堆栈信息,定位到具体发生递归的方法。 但是,我们也可以检查一下`isUpstreamNode`方法中是否存在间接递归的可能性?比如,在遍历节点时,某个节点的上游节点又指向了当前节点,形成环,而我们的循环没有处理环,导致无限循环?注意,无限循环不会导致栈溢出,而是CPU占满。 所以,栈溢出一定是由于方法调用嵌套过深,即递归调用。 重新审视代码,发现一个可能的递归点:在`findReturnTaskList`方法中,我们调用了`isValidReturnNode`,而`isValidReturnNode`又调用了`isUpstreamNode`,这都没有问题。但是,如果`isUpstreamNode`方法中又调用了其他方法,而这些方法又调用了`findReturnTaskList`,就会形成递归。 但我们的`isUpstreamNode`方法中并没有调用其他服务,只是使用了流程模型的结构信息。 因此,我们可能需要用户提供更多的上下文代码,特别是栈溢出异常的堆栈跟踪。 鉴于问题描述有限,我们只能给出一般性建议: 1. 检查栈溢出异常堆栈,确定递归发生的位置。 2. 检查流程回退操作的整体逻辑,避免在循环或递归调用中嵌套过深。 3. 在`isUpstreamNode`方法中添加环检测,避免在流程有环的情况下无限循环(虽然不会栈溢出,但是也是问题)。 针对环检测,我们可以修改`isUpstreamNode`方法,记录已访问的节点: ```java private boolean isUpstreamNode(BpmnModelInstance model, FlowElement current, FlowElement target) { Set<String> visited = new HashSet<>(); FlowElement node = current; while (node != null) { // 如果已经访问过这个节点,说明有环,退出 if (visited.contains(node.getId())) { break; } visited.add(node.getId()); // 获取当前节点的所有输入流 if (!(node instanceof FlowNode)) { break; } Collection<SequenceFlow> incoming = ((FlowNode) node).getIncoming(); if (incoming.isEmpty()) { break; } boolean found = false; for (SequenceFlow flow : incoming) { FlowNode source = (FlowNode) flow.getSource(); if (source.getId().equals(target.getId())) { return true; } // 检查这个上游节点是否在visited中?在下一轮循环开始时会检查 node = source; found = true; break; // 这里我们只选择一条路径回溯?还是需要回溯所有路径? } // 如果没有找到下一个节点(即没有break,但实际上上面的循环每次只处理一个流?这里逻辑有问题) // 上面的循环每次只处理第一个流入的流?这会导致只回溯一条路径,可能遗漏其他路径。 // 因此,我们需要修改为遍历所有流入流,只要有一条路径能追溯到目标节点就返回true。 // 所以,这里应该使用深度优先搜索(DFS)或广度优先搜索(BFS)来遍历所有上游路径。 } return false; } ``` 上述代码存在逻辑问题:它只回溯了第一条流入路径,而实际上一个节点可能有多个流入路径(比如在合并网关之后)。因此,我们需要使用图遍历算法(DFS)来遍历所有可能的路径。 修改为DFS实现: ```java private boolean isUpstreamNode(BpmnModelInstance model, FlowElement current, FlowElement target) { Set<String> visited = new HashSet<>(); return dfsUpstream((FlowNode) current, (FlowNode) target, visited); } private boolean dfsUpstream(FlowNode currentNode, FlowNode targetNode, Set<String> visited) { // 避免循环 if (visited.contains(currentNode.getId())) { return false; } visited.add(currentNode.getId()); // 遍历当前节点的所有流入边 for (SequenceFlow flow : currentNode.getIncoming()) { FlowNode source = (FlowNode) flow.getSource(); if (source.getId().equals(targetNode.getId())) { return true; } // 递归检查上游节点 if (dfsUpstream(source, targetNode, visited)) { return true; } } return false; } ``` 这个DFS实现是递归的,当流程很长时可能导致栈溢出。这就是用户遇到栈溢出的原因吗?因为我们之前使用的是循环,而后来改成了递归的DFS。 但是,在之前的消息中,我们提供的`isUpstreamNode`是循环迭代实现,并没有使用递归。用户是在使用循环迭代版本时出现栈溢出,还是在使用DFS递归版本时出现? 回顾我们之前提供的代码,在回答“为什么在hr人事节点还是为空”时,我们提供的`isUpstreamNode`是循环迭代版本。所以用户应该使用的是迭代版本,不会因为递归导致栈溢出。 因此,栈溢出可能发生在其他地方。 由于问题复杂且信息有限,我们建议: 1. 检查完整的异常堆栈,定位问题。 2. 如果确实在DFS递归版本中发生栈溢出,可以改为使用栈的迭代DFS(非递归)来避免。 迭代DFS实现示例: ```java private boolean isUpstreamNode(BpmnModelInstance model, FlowElement current, FlowElement target) { if (!(current instanceof FlowNode) || !(target instanceof FlowNode)) { return false; } FlowNode targetNode = (FlowNode) target; Set<String> visited = new HashSet<>(); Deque<FlowNode> stack = new ArrayDeque<>(); stack.push((FlowNode) current); while (!stack.isEmpty()) { FlowNode node = stack.pop(); if (node.getId().equals(targetNode.getId())) { return true; } // 避免环 if (visited.contains(node.getId())) { continue; } visited.add(node.getId()); // 将所有上游节点入栈 for (SequenceFlow flow : node.getIncoming()) { FlowNode source = (FlowNode) flow.getSource(); if (!visited.contains(source.getId())) { stack.push(source); } } } return false; } ``` 这个迭代DFS使用栈来模拟递归,避免了递归调用导致的栈溢出。 ### 总结建议 1. **检查异常堆栈**:确定栈溢出的具体位置。 2. **优化上游节点检测**:使用迭代DFS代替递归DFS,避免深递归。 3. **添加环检测**:防止在流程有环时出现无限循环或无限递归。 如果用户能提供更多错误信息,我们可以进一步分析。目前,我们提供迭代DFS的实现来替代可能的递归实现,以避免栈溢出。 §§相关问题§§1. 在流程回退功能中如何避免因流程环路导致的无限循环?2. 迭代DFS与递归DFS在流程节点回溯中的性能差异有哪些?3. Camunda中如何获取流程模型的环路信息?4. 除了栈溢出,流程回退功能还可能遇到哪些常见异常?5. 如何通过流程模型优化避免深层次的节点嵌套?</think>### 栈溢出问题分析与解决 您在流程回退功能中遇到的栈溢出错误,主要是由于深度优先搜索(DFS)递归实现导致的。当流程路径较长或存在复杂网关结构时,递归层级过深会超出JVM栈空间。以下是具体分析和解决方案: #### 问题原因 1. **递归深度过大**:DFS递归实现在长流程链中调用层级过深 2. **环状流程风险**:未检测循环引用导致无限递归 3. **JVM栈限制**:默认栈大小(通常1MB)不足以支持复杂流程 #### 迭代DFS解决方案 ```java private boolean isUpstreamNode(FlowNode current, FlowNode target) { Set<String> visited = new HashSet<>(); // 环状流程检测 Deque<FlowNode> stack = new ArrayDeque<>(); stack.push(current); while (!stack.isEmpty()) { FlowNode node = stack.pop(); // 环检测 if (visited.contains(node.getId())) continue; visited.add(node.getId()); // 找到目标节点 if (node.getId().equals(target.getId())) { return true; } // 添加上游节点 for (SequenceFlow flow : node.getIncoming()) { FlowNode source = (FlowNode) flow.getSource(); if (!visited.contains(source.getId())) { stack.push(source); } } } return false; } ``` #### 关键优化点 1. **栈溢出预防**: ```java // 使用显式栈替代递归 Deque<FlowNode> stack = new ArrayDeque<>(); while (!stack.isEmpty()) { ... } ``` 2. **环状流程处理**: ```java Set<String> visited = new HashSet<>(); if (visited.contains(node.getId())) continue; ``` 3. **内存优化**: ```java // 限制搜索深度(可选) if (visited.size() > MAX_DEPTH) break; ``` #### 完整优化实现 ```java private static final int MAX_DEPTH = 50; // 最大搜索深度 private boolean isUpstreamNode(FlowNode current, FlowNode target) { // 快速检查直接上游 if (isDirectUpstream(current, target)) return true; Set<String> visited = new HashSet<>(); Deque<FlowNode> stack = new ArrayDeque<>(); stack.push(current); while (!stack.isEmpty() && visited.size() <= MAX_DEPTH) { FlowNode node = stack.pop(); if (visited.contains(node.getId())) continue; visited.add(node.getId()); if (node.getId().equals(target.getId())) { return true; } for (SequenceFlow flow : node.getIncoming()) { FlowNode source = (FlowNode) flow.getSource(); if (!visited.contains(source.getId())) { stack.push(source); } } } return false; } // 直接上游节点检查(优化性能) private boolean isDirectUpstream(FlowNode current, FlowNode target) { for (SequenceFlow flow : current.getIncoming()) { if (flow.getSource().getId().equals(target.getId())) { return true; } } return false; } ``` ### 最佳实践建议 1. **深度监控**: ```java // 添加日志监控 log.debug("节点回溯深度: {}", visited.size()); if (visited.size() > 30) { log.warn("深度回溯可能影响性能: {}", current.getId()); } ``` 2. **网关特殊处理**: ```java // 处理并行网关 if (node instanceof ParallelGateway) { handleParallelGateway((ParallelGateway) node, stack, visited); } ``` 3. **JVM参数调整**(临时方案): ```bash # 增加栈大小 java -Xss4m -jar your_application.jar ``` ### 性能对比 | 方法 | 时间复杂度 | 空间复杂度 | 栈溢出风险 | |------|------------|------------|------------| | 递归DFS | O(N) | O(N) | 高 | | 迭代DFS | O(N) | O(N) | 无 | | 广度优先(BFS) | O(N) | O(N) | 无 | **建议**:对于深度>50的流程,考虑使用BFS算法替代DFS
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值