我正在使用 openjdk 版本的 Nashorn 15.4,使用 Java 21 和 Java 11 进行测试,结果相同。
我的代码加载 Nashorn 脚本引擎绑定表示元数据字段的值,并允许用户编写 Javascript 来修改这些元数据字段,方法是将脚本传递给 eval 方法,然后检查修改后的元数据值。它工作正常,但随着我增加了添加到绑定的值的数量,我遇到了意想不到的问题,尽管脚本没有修改这些值,但某些绑定变量值丢失或更改。
提供一些背景信息的屏幕截图
我现在可以用独立的示例来模拟问题
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
public class JsTest
{
public static void main(String[] args) throws Exception
{
int numberOfBindingVars = Integer.parseInt(args[0]);
ScriptEngine engine;
for(int i=0; i<10; i++)
{
engine = new org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory().getScriptEngine();
for(int k=0;k<numberOfBindingVars;k++)
{
engine.put("var"+k,"1963");
}
System.out.println(String.format("Bindings %d", engine.getBindings(ScriptContext.ENGINE_SCOPE).size()));
for(int k=0;k<numberOfBindingVars;k++)
{
if(engine.get("var"+k)==null)
{
System.out.println("Missing var"+k);
}
}
}
}
}
使用参数 400 运行它效果很好,并给了我
Bindings 400
Bindings 400
Bindings 400
Bindings 400
Bindings 400
Bindings 400
Bindings 400
Bindings 400
Bindings 400
Bindings 400
但是,如果我将参数增加到500,则绑定计数不一致,尽管奇怪地检查每个值并没有发现任何缺失。
Bindings 500
Bindings 499
Bindings 498
Bindings 497
Bindings 496
Bindings 495
Bindings 494
Bindings 500
Bindings 499
Bindings 498
如果使用 1000 运行,我会再次得到不一致的计数,也无法在 500 标记附近找到某些变量
Bindings 1000
Bindings 999
Missing var448
Bindings 1000
Bindings 999
Missing var448
Bindings 998
Missing var448
Missing var449
Bindings 997
Missing var448
Missing var449
Missing var450
Bindings 996
Missing var448
Missing var449
Missing var450
Missing var451
Bindings 995
Missing var448
Missing var449
Missing var450
Missing var451
Missing var452
Bindings 994
Missing var448
Missing var449
Missing var450
Missing var451
Missing var452
Missing var453
Bindings 993
Missing var448
Missing var449
Missing var450
Missing var451
Missing var452
Missing var453
Missing var454
在所有情况下,第一次都可以正常工作,但随着 500 次和 1000 次调用,后续迭代就不正确。
更多测试对于 448 个绑定总是没问题,但对于 449 或更大的绑定则失败。
似乎有其他一些 Nashorn 进程在修剪 Bindings 的大小,这肯定是一个错误?
希望获得一些对 nashorn 更深入了解的意见
Java
$ java -version
openjdk version "17.0.12" 2024-07-16
OpenJDK Runtime Environment Temurin-17.0.12+7 (build 17.0.12+7)
OpenJDK 64-Bit Server VM Temurin-17.0.12+7 (build 17.0.12+7, mixed mode, sharing)
jsEngineScript
├── pom.xml
└── src
└── main
└── java
└── com
└── example
└── JsTest.java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>js-engine-script</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>jsEngineScript</name>
<description>jsEngineScript App Project</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.openjdk.nashorn</groupId>
<artifactId>nashorn-core</artifactId>
<version>15.4</version>
</dependency>
</dependencies>
<build>
<finalName>app</finalName>
</build>
</project>
static void forceGC()
:执行垃圾收集showJVMMem()
:显示JVM内存设置package com.example;
import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
public class JsTest {
public static void main(String[] args) throws Exception {
boolean enableForceGC = false;
//default for loop = 10;
int inputForLoop = 10;
if (args.length >= 2) {
for (int x = 1; x < args.length; x++) {
if (args[x].equals("--enableForceGC")) {
enableForceGC = true;
}
if (args[x].startsWith("--inputFor=")) {
String inputForNum = args[x].substring("--inputFor=".length());
//System.out.println("inputForNum = " + inputForNum);
inputForLoop = Integer.parseInt(inputForNum);
}
}
}
showJVMMem();
int numberOfBindingVars = Integer.parseInt(args[0]);
System.out.println(">>>> numberOfBindingVars = " + numberOfBindingVars);
System.out.println(">>>> inputForLoop = " + inputForLoop);
System.out.println(">>>> enableForceGC = " + enableForceGC);
System.out.println();
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine;
//for(int i=0; i<10; i++)
for (int i = 0; i < inputForLoop; i++) {
System.out.println(">>>> i = " + i);
//engine = new org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory().getScriptEngine();
engine = manager.getEngineByName("JavaScript");
int kct = 0;
for (int k = 0; k < numberOfBindingVars; k++) {
engine.put("var" + k, "1963");
kct = kct + 1;
} //for k1
System.out.println("i = " + i + "\tk count=" + kct);
System.out.printf("i = " + i + "\tBindings %d%n", engine.getBindings(ScriptContext.ENGINE_SCOPE).size());
// Traverse Bindings and output all variable names and corresponding values
/*
Bindings myBindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
System.out.println("i = " + i + "\tbindings size = " + myBindings.size());
System.out.println("i = " + i +"\tBindings List:");
System.out.println();
for (String key : myBindings.keySet()) {
System.out.println(key + " = " + myBindings.get(key));
}
*/
for (int k = 0; k < numberOfBindingVars; k++) {
if (engine.get("var" + k) == null) {
System.out.println("Missing var" + k);
}
} //for k2
engine = null;
if (enableForceGC) forceGC();
System.out.println(">>>>END i = " + i);
System.out.println();
} //for i
} //main
static void forceGC() {
System.out.println();
System.out.println(">>>> run forceGC()");
System.gc(); // Or use Runtime.getRuntime().gc();
// Wait a while to allow garbage collection to complete
try {
Thread.sleep(1000); // Pause 1 second
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static void showJVMMem() {
// Get a Runtime instance
Runtime runtime = Runtime.getRuntime();
// Output the total memory of the JVM
long totalMemory = runtime.totalMemory();
// Output the maximum memory of the JVM
long maxMemory = runtime.maxMemory();
// Output the used memory of the JVM
long usedMemory = totalMemory - runtime.freeMemory();
// Get MemoryMXBean
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
// Get heap memory usage
MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();
// Output memory usage information
System.out.println(">>>> Heap Memory Usage:");
System.out.println("Initial Memory (Xms): " + formatSize(heapMemoryUsage.getInit()));
System.out.println("Used Memory: " + formatSize(heapMemoryUsage.getUsed()));
System.out.println("Max Memory (Xmx): " + formatSize(heapMemoryUsage.getMax()));
System.out.println();
System.out.println(">>>> JVM Memory Usage:");
System.out.println("Total Memory: " + formatSize(totalMemory));
System.out.println("Used Memory: " + formatSize(usedMemory));
System.out.println("Free Memory: " + formatSize(runtime.freeMemory()));
System.out.println("Max Memory: " + formatSize(maxMemory));
System.out.println();
}
static String formatSize(long size) {
String[] units = new String[]{"B", "KB", "MB", "GB", "TB"};
int unitIndex = 0;
double sizeInUnit = size;
while (sizeInUnit >= 1024 && unitIndex < units.length - 1) {
sizeInUnit /= 1024;
unitIndex++;
}
return String.format("%.2f %s", sizeInUnit, units[unitIndex]);
}
} //class
mvn clean package
mvn dependency:copy-dependencies -DoutputDirectory=target/libs
java -cp "target/libs/*:target/app.jar" \
com.example.JsTest 1000 \
>> result_for_10_Binding_1K_NoGC.txt
java -cp "target/libs/*:target/app.jar" \
com.example.JsTest 10000 \
>> result_for_10_Binding_10K_NoGC.txt
java -cp "target/libs/*:target/app.jar" \
com.example.JsTest 100000 \
>> result_for_10_Binding_100K_NoGC.txt
java -cp "target/libs/*:target/app.jar" \
com.example.JsTest 1000 \
--enableForceGC \
>> result_for_10_Binding_1K_ForceGC.txt
java -cp "target/libs/*:target/app.jar" \
com.example.JsTest 10000 \
--enableForceGC \
>> result_for_10_Binding_10K_ForceGC.txt
java -cp "target/libs/*:target/app.jar" \
com.example.JsTest 100000 \
--enableForceGC \
>> result_for_10_Binding_100K_ForceGC.txt
我先给出结论:
(a) 在for循环中添加强制垃圾回收可以避免该问题。
(b) 如果不添加强制垃圾回收,给定不同的参数值,将会有不同的结果。当测试 paramter = 1000 (1K ), paramter = 10000 (10K), paramter = 100000 (100K) 时会有不同的结果。
您可以打开txt文件并搜索
Missing
。如果没有出现Missing
,说明可以使用gc来解决问题。
>>>> Heap Memory Usage:
Initial Memory (Xms): 500.00 MB
Used Memory: 0.00 B
Max Memory (Xmx): 7.81 GB
>>>> JVM Memory Usage:
Total Memory: 508.00 MB
Used Memory: 4.88 MB
Free Memory: 501.68 MB
Max Memory: 7.81 GB
>>>> numberOfBindingVars = 100000
>>>> inputForLoop = 10
>>>> enableForceGC = true
>>>> i = 0
i = 0 k count=100000
i = 0 Bindings 100000
>>>> run forceGC()
>>>>END i = 0
>>>> i = 1
i = 1 k count=100000
i = 1 Bindings 100000
>>>> run forceGC()
>>>>END i = 1
>>>> i = 2
i = 2 k count=100000
i = 2 Bindings 100000
>>>> run forceGC()
>>>>END i = 2
>>>> i = 3
i = 3 k count=100000
i = 3 Bindings 100000
>>>> run forceGC()
>>>>END i = 3
>>>> i = 4
i = 4 k count=100000
i = 4 Bindings 100000
>>>> run forceGC()
>>>>END i = 4
>>>> i = 5
i = 5 k count=100000
i = 5 Bindings 100000
>>>> run forceGC()
>>>>END i = 5
>>>> i = 6
i = 6 k count=100000
i = 6 Bindings 100000
>>>> run forceGC()
>>>>END i = 6
>>>> i = 7
i = 7 k count=100000
i = 7 Bindings 100000
>>>> run forceGC()
>>>>END i = 7
>>>> i = 8
i = 8 k count=100000
i = 8 Bindings 100000
>>>> run forceGC()
>>>>END i = 8
>>>> i = 9
i = 9 k count=100000
i = 9 Bindings 100000
>>>> run forceGC()
>>>>END i = 9
我必须告诉你,有趣的结果发生了。
发现
Missing
:29次,当for循环=10时,输入参数为1000(1K)。
发现
Missing
:1次,for循环=10时,输入参数为10000(10K)。
发现
Missing
:0次,当for循环=10时,输入参数为100000(100K)。
>>>> Heap Memory Usage:
Initial Memory (Xms): 500.00 MB
Used Memory: 0.00 B
Max Memory (Xmx): 7.81 GB
>>>> JVM Memory Usage:
Total Memory: 508.00 MB
Used Memory: 4.88 MB
Free Memory: 501.68 MB
Max Memory: 7.81 GB
>>>> numberOfBindingVars = 100000
>>>> inputForLoop = 10
>>>> enableForceGC = false
>>>> i = 0
i = 0 k count=100000
i = 0 Bindings 100000
>>>>END i = 0
>>>> i = 1
i = 1 k count=100000
i = 1 Bindings 100000
>>>>END i = 1
>>>> i = 2
i = 2 k count=100000
i = 2 Bindings 100000
>>>>END i = 2
>>>> i = 3
i = 3 k count=100000
i = 3 Bindings 100000
>>>>END i = 3
>>>> i = 4
i = 4 k count=100000
i = 4 Bindings 100000
>>>>END i = 4
>>>> i = 5
i = 5 k count=100000
i = 5 Bindings 100000
>>>>END i = 5
>>>> i = 6
i = 6 k count=100000
i = 6 Bindings 100000
>>>>END i = 6
>>>> i = 7
i = 7 k count=100000
i = 7 Bindings 100000
>>>>END i = 7
>>>> i = 8
i = 8 k count=100000
i = 8 Bindings 100000
>>>>END i = 8
>>>> i = 9
i = 9 k count=100000
i = 9 Bindings 100000
>>>>END i = 9
示例:
java \
-Xmx512m \
-Xms256m \
-Xss1m \
-cp "target/libs/*:target/app.jar" \
com.example.JsTest 6000 \
--inputFor=20 \
--enableForceGC
--inputFor=20
,for循环默认为10。您可以将for循环配置为1或20。--enableForceGC
,你可以添加这个来强制GC。您还可以调整 JVM 的内存设置:
-Xmx512m
-Xms256m
-Xss1m
您可以不断调整内存设置,看看是否有影响。