Nashorn引擎添加大量键值对后出现变量未定义异常排查
背景
我在Java应用中使用Nashorn 15.3 JavaScript引擎,允许用户通过掩码变量(约200个)基于元数据重命名音乐文件,该功能已正常运行多年。
为支持多值字段的索引访问,我为每个变量新增10个带_index后缀的索引变量(总计约2000个),代码如下:
List<String> values = song.getFieldValues(next.getSongFieldKey()); for(int i=0; i < 10;i++) { if(values.size()>i) { engine.put(next.getScriptVar() + "_index" + i,values.get(i)); } else { engine.put(next.getScriptVar() + "_index" + i, ""); } }
问题出现
此后出现随机报错:
javax.script.ScriptException: ReferenceError: "discno" is not defined in <eval> at line number 81 at org.openjdk.nashorn.api.scripting.NashornScriptEngine.throwAsScriptException(NashornScriptEngine.java:463) at org.openjdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:447) at org.openjdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:399) at org.openjdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:395) at org.openjdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:151) at java.scripting/javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:262)
即使discno变量已被明确添加到引擎绑定中。尝试过engine.getBindings(ScriptContext.ENGINE_SCOPE).clear()清空绑定、添加同步代码等操作,均无法解决问题;但移除索引变量后功能恢复正常,不过新增的多值索引访问功能也随之丢失。
解决方法
后续将索引变量改为数组存储,代码调整如下:
engine.put(next.getScriptVar(), cleanValue(next.getSongFieldKey(), song.getFieldValueSemiColonAndSpaceSeparatedOrEmptyString(next.getSongFieldKey()))); addIndexedValues( engine, song, next); private static void addIndexedValues( ScriptEngine engine, Song song, SongFieldName fieldName) { List<String> values = song.getFieldValues(fieldName.getSongFieldKey()); String[] value = new String[INDEX_SIZE]; for(int i=0; i < INDEX_SIZE;i++) { if(values.size()>i) { value[i] = cleanValue(fieldName.getSongFieldKey(), values.get(i)); } else { value[i] = ""; } } engine.put(fieldName.getScriptVar() + "_index", value); }
调整后问题完全解决,以下是问题根源分析:
根源分析
哈希表负载过高引发冲突
Nashorn的ENGINE_SCOPE绑定基于哈希表实现,当绑定变量从200暴增至2000个时,哈希表负载系数急剧上升,触发大量哈希冲突。极端情况下,冲突会导致键值对的存储或查找异常,表现为随机的变量未定义错误——即使代码逻辑已添加变量,哈希表查找仍无法定位对应值。索引变量命名的哈希聚集性
新增的索引变量命名为原变量名_index数字,这类命名的哈希值分布存在聚集性,进一步提升了哈希冲突概率。改用数组存储后,每个原变量仅对应一个数组类型的绑定项,总绑定数量回到200左右,哈希表负载系数恢复正常,冲突概率大幅降低。Nashorn引擎的局限性
Nashorn作为JDK15后被标记废弃的老引擎,其绑定处理逻辑未针对大量变量场景做充分优化。当绑定数量超过阈值时,内部变量解析或查找逻辑可能出现异常,而同步代码无效的原因是问题并非来自多线程竞争,而是引擎内部哈希表的实现缺陷。
内容的提问来源于stack exchange,提问作者Paul Taylor

