OptaPlanner Shadow Variables理解及栈溢出报错问题求助
问题根因说明
你的栈溢出错误和Shadow Variables本身的运行逻辑无关,是两个代码写法错误共同导致的:
- Lombok @Data注解触发双向引用递归
@Data注解会自动生成包含类下所有字段的hashCode()、equals()、toString()方法。你的代码里CloudProcess持有CloudComputer类型的规划变量字段,CloudComputer又持有List<CloudProcess>类型的影子变量字段,形成了双向引用链:计算CloudProcess的hashCode → 计算其内部cloudComputer字段的hashCode → 遍历cloudComputer的processList计算每个CloudProcess的hashCode → 回到第一步无限递归
递归深度超过JVM栈限制就会抛出StackOverflowError,从错误栈里交替出现CloudComputer.hashCode、CloudProcess.hashCode、ArrayList.hashCode就能直接确认这个问题。 - 实体注解标注错误
CloudComputer是固定的问题事实(求解过程中不会修改它的属性),你已经在CloudBalance类里把computerList标注为@ProblemFactCollectionProperty,就不应该给CloudComputer类加@PlanningEntity注解,多余的实体注解会干扰OptaPlanner的正常扫描逻辑。
Shadow Variables(影子变量)运行机制
OptaPlanner里的变量分两类:
- 真正的规划变量:就是加了
@PlanningVariable注解的字段,是求解器求解过程中会主动调整、赋值的核心变量,比如你代码里CloudProcess的cloudComputer字段,求解器会不断尝试给每个进程分配不同的机器,最终找到分数最高的分配方案。 - 影子变量:求解器不会直接给这类字段赋值,它的值会自动和一个或多个规划变量的状态保持同步,作用是简化关联关系的维护,写约束的时候不用每次都全量遍历实体集合做反向查找。
你用的@InverseRelationShadowVariable是最常用的影子变量类型,属于反向关联影子变量:指定sourceVariableName = "cloudComputer"后,只要某个CloudProcess的cloudComputer字段被求解器修改,OptaPlanner会自动同步维护两边的关联关系:
- 进程被分配给某台机器时,自动把进程加入对应机器的
processList - 进程从一台机器迁移到另一台时,自动从原机器的
processList移除,加入新机器的processList
整个同步过程不需要你写任何额外的维护代码。
修复方案
按以下步骤调整代码即可解决问题:
- 去掉
CloudComputer类上的@PlanningEntity注解,它属于问题事实,不需要标注为规划实体。 - 处理双向引用的递归问题,不要直接在存在双向关联的规划实体/问题事实类上使用无配置的
@Data注解:- 给
CloudComputer、CloudProcess添加唯一标识id字段,基于id重写hashCode()和equals()方法,不要把双向关联字段纳入计算 - 如果暂时不添加id,使用Lombok注解排除双向关联字段:
// CloudProcess类上添加注解,排除双向关联的cloudComputer字段 @EqualsAndHashCode(exclude = "cloudComputer") @ToString(exclude = "cloudComputer")// CloudComputer类上添加注解,排除双向关联的processList字段 @EqualsAndHashCode(exclude = "processList") @ToString(exclude = "processList")
- 给
调整后重启求解即可正常运行,影子变量的关联维护逻辑会自动生效。
内容的提问来源于stack exchange,提问作者Joy
相关产品推荐
相关产品推荐

