Optaplanner基准测试XML复现Java求解器时触发空指针异常
问题:OptaPlanner基准测试XML配置触发空指针异常(Probabilistic Change Move Selector场景)
基于Gradle使用OptaPlanner 9.44.0.Final开发的求解器,Java代码运行正常,包含Probabilistic Change Move Selector。转成XML配置用于基准测试时触发空指针异常,相关代码及异常信息如下,寻求排查解决方法。
Java求解器代码
public SolverFactory<OptaplannerSolution> customSolverFactory_10(OptaplannerAlgorithm params) { VehicleDriverTimetableOptaplannerAlgorithm par = (VehicleDriverTimetableOptaplannerAlgorithm) params; LOGGER.info("============================================="); LOGGER.info("=====================Solution==================="); ChangeMoveSelectorConfig cms = new ChangeMoveSelectorConfig(); cms.setSelectionOrder(SelectionOrder.PROBABILISTIC); cms.setCacheType(SelectionCacheType.STEP); cms.setSelectedCountLimit(100l); cms.setProbabilityWeightFactoryClass(SelectionProbabilityWeightFactoryM.class); ConstructionHeuristicForagerConfig forager = new ConstructionHeuristicForagerConfig(); forager.setPickEarlyType(ConstructionHeuristicPickEarlyType.FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD); ConstructionHeuristicPhaseConfig ch = new ConstructionHeuristicPhaseConfig() .withConstructionHeuristicType(ConstructionHeuristicType.FIRST_FIT_DECREASING) .withForagerConfig(forager); ch.setMoveSelectorConfigList(Collections.singletonList(cms)); LocalSearchForagerConfig localSearchForager = new LocalSearchForagerConfig(); localSearchForager.setAcceptedCountLimit(100); localSearchForager.setPickEarlyType(LocalSearchPickEarlyType.FIRST_LAST_STEP_SCORE_IMPROVING); LocalSearchAcceptorConfig localSearchAcceptor = new LocalSearchAcceptorConfig(); localSearchAcceptor.setEntityTabuSize(10); LocalSearchPhaseConfig ls= new LocalSearchPhaseConfig().withForagerConfig(localSearchForager ).withAcceptorConfig(localSearchAcceptor); ls.setMoveSelectorConfig(cms); ScoreDirectorFactoryConfig sd = new ScoreDirectorFactoryConfig() .withEasyScoreCalculatorClass(VehicleDriverTimetableOptaplannerScoreCalculator.class); return SolverFactory.create(new SolverConfig().withSolutionClass(VehicleDriverTimetableOptaplannerSolution.class) .withEntityClasses(OPService.class) .withPhases(ch,ls) .withScoreDirectorFactory(sd) .withTerminationConfig(new TerminationConfig() .withUnimprovedSecondsSpentLimit((long) par.maxUnimproved) .withBestScoreLimit("0hard/0medium/0soft").withSecondsSpentLimit((long) par.maxTime))); }
基准测试XML配置
<solverBenchmark> <name>BASE</name> <solver> <constructionHeuristic> <constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType> <unionMoveSelector> <changeMoveSelector> <cacheType>STEP</cacheType> <selectionOrder>PROBABILISTIC</selectionOrder> <probabilityWeightFactoryClass>org.eurecat.engine.dtos.problems.vehicle_driver_timetable.SelectionProbabilityWeightFactoryM</probabilityWeightFactoryClass> <selectedCountLimit>100</selectedCountLimit> </changeMoveSelector> </unionMoveSelector> <forager> <pickEarlyType>FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD</pickEarlyType> </forager> </constructionHeuristic> <localSearch> <acceptor> <entityTabuSize>10</entityTabuSize> </acceptor> <forager> <pickEarlyType>FIRST_LAST_STEP_SCORE_IMPROVING</pickEarlyType> <acceptedCountLimit>100</acceptedCountLimit> </forager> </localSearch> </solver> </solverBenchmark>
异常信息
Exception in thread "main" org.optaplanner.benchmark.api.PlannerBenchmarkException: Benchmarking failed: failureCount (1). The exception of the firstFailureSingleBenchmarkRunner (Problem_0_BASE_0) is chained.at org.optaplanner.benchmark.impl.DefaultPlannerBenchmark.benchmarkingEnded(DefaultPlannerBenchmark.java:311)at org.optaplanner.benchmark.impl.DefaultPlannerBenchmark.benchmark(DefaultPlannerBenchmark.java:84)at org.optaplanner.benchmark.impl.DefaultPlannerBenchmark.benchmarkAndShowReportInBrowser(DefaultPlannerBenchmark.java:409)at org.eurecat.engine.Benchmarking.main(Benchmarking.java:225)Caused by: java.lang.NullPointerException: Cannot invoke "java.util.NavigableMap.size()" because "this.cachedMoveMap" is nullat org.optaplanner.core.impl.heuristic.selector.move.decorator.ProbabilityMoveSelector.getSize(ProbabilityMoveSelector.java:87)at org.optaplanner.core.impl.heuristic.selector.move.decorator.SelectedCountLimitMoveSelector.getSize(SelectedCountLimitMoveSelector.java:42)at org.optaplanner.core.impl.heuristic.selector.IterableSelector.spliterator(IterableSelector.java:24)at org.optaplanner.core.impl.heuristic.selector.move.composite.UnionMoveSelector.toStream(UnionMoveSelector.java:114)at org.optaplanner.core.impl.heuristic.selector.move.composite.UnionMoveSelector.iterator(UnionMoveSelector.java:94)at org.optaplanner.core.impl.constructionheuristic.placer.QueuedEntityPlacer$QueuedEntityPlacingIterator.createUpcomingSelection(QueuedEntityPlacer.java:56)at org.optaplanner.core.impl.constructionheuristic.placer.QueuedEntityPlacer$QueuedEntityPlacingIterator.createUpcomingSelection(QueuedEntityPlacer.java:31)at org.optaplanner.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator.hasNext(UpcomingSelectionIterator.java:27)at org.optaplanner.core.impl.constructionheuristic.DefaultConstructionHeuristicPhase.solve(DefaultConstructionHeuristicPhase.java:45)at org.optaplanner.core.impl.solver.AbstractSolver.runPhases(AbstractSolver.java:83)at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:193)at org.optaplanner.benchmark.impl.SubSingleBenchmarkRunner.call(SubSingleBenchmarkRunner.java:108)at org.optaplanner.benchmark.impl.SubSingleBenchmarkRunner.call(SubSingleBenchmarkRunner.java:28)at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)at java.base/java.lang.Thread.run(Thread.java:833)
排查与解决方法
异常原因分析
空指针异常来自ProbabilityMoveSelector.getSize()方法,因cachedMoveMap未初始化导致。对比Java代码与XML配置的核心差异:
- Java代码中,构造启发式阶段直接将
ChangeMoveSelectorConfig设置为阶段的移动选择器列表,未使用UnionMoveSelector;但XML配置中用<unionMoveSelector>包裹了changeMoveSelector,这会改变OptaPlanner的初始化逻辑,导致概率选择器的缓存未被正确初始化。 - XML的LocalSearch阶段缺少Java代码中配置的
ChangeMoveSelector,配置不一致可能引发其他潜在问题。
修复步骤
- 移除构造启发式中的
UnionMoveSelector,直接将changeMoveSelector作为constructionHeuristic的子元素,与Java代码对齐:
<constructionHeuristic> <constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType> <changeMoveSelector> <cacheType>STEP</cacheType> <selectionOrder>PROBABILISTIC</selectionOrder> <probabilityWeightFactoryClass>org.eurecat.engine.dtos.problems.vehicle_driver_timetable.SelectionProbabilityWeightFactoryM</probabilityWeightFactoryClass> <selectedCountLimit>100</selectedCountLimit> </changeMoveSelector> <forager> <pickEarlyType>FIRST_FEASIBLE_SCORE_OR_NON_DETERIORATING_HARD</pickEarlyType> </forager> </constructionHeuristic>
- 补充LocalSearch阶段的移动选择器配置,确保与Java版本的求解器配置完全一致:
<localSearch> <acceptor> <entityTabuSize>10</entityTabuSize> </acceptor> <forager> <pickEarlyType>FIRST_LAST_STEP_SCORE_IMPROVING</pickEarlyType> <acceptedCountLimit>100</acceptedCountLimit> </forager> <changeMoveSelector> <cacheType>STEP</cacheType> <selectionOrder>PROBABILISTIC</selectionOrder> <probabilityWeightFactoryClass>org.eurecat.engine.dtos.problems.vehicle_driver_timetable.SelectionProbabilityWeightFactoryM</probabilityWeightFactoryClass> <selectedCountLimit>100</selectedCountLimit> </changeMoveSelector> </localSearch>
验证概率权重工厂类的可访问性:确保
SelectionProbabilityWeightFactoryM是public类,XML中的全类名拼写正确,且基准测试的类路径能加载到该类。确认缓存与选择顺序的兼容性:
PROBABILISTIC选择器搭配STEP缓存类型合法,移除UnionMoveSelector后,构造启发式的初始化逻辑会与Java代码一致,触发cachedMoveMap的正确创建。
内容的提问来源于stack exchange,提问作者Cristian Alarcón Sanabria
相关产品推荐
相关产品推荐

