Java VM是否有缺陷,或者为什么反复使用CompliationTask和反射会导致软件缺陷?
问题已解决:以下代码在Java 1.8.232中仍然存在缺陷,但在Java 13.0.1中具有正确的行为。也许OpenJDK的人可以研究这个问题,因为Java 8仍然受支持?还不知道是什么版本解决了Java8和Java13之间的这个问题
Java虚拟机有缺陷吗?或者为什么重复使用Java CompilationTask、ToolProvider。getSystemJavaCompiler和反射导致软件缺陷
以下代码已从原始代码基线(https://github.com/peytonc)中缩减,并使用了运行时代码生成、运行时编译和反射API
在下面的软件中,重复调用Java编译器(在运行时编译动态代码)和反射(调用动态代码)会出现缺陷。起初,所有调用都会执行正确的计算,但经过一段较长的时间后,计算会变得不正确。当软件按预期运行时,JVM只保留每个唯一包/类的一个实例(在我看来这是正确的)。但是,当缺陷发生时,JVM有两个或多个相同包/类的副本(在我看来这是不正确的)
在我的计算机上[Intel NUC,Fedora 31,OpenJDK运行时环境(build 1.8.0_232-b09)],下面的代码生成以下结果。迭代0-913都显示正确的行为,而迭代914显示第一个有缺陷的行为
INFO: Iteration #0: (c0) (i0) (c1) (i1) (c2) (i2) (c4) (i4) (c3) (i3) (c5) (i5) (c6) (i6) (c7) (i7) (c8) (i8) (c9) (i9)
INFO: Iteration #1: (c0) (i0) (c1) (i1) (c4) (i4) (c5) (c6) (i5) (i6) (c7) (i7) (c8) (i8) (c9) (i9) (c3) (i3) (c2) (i2)
INFO: Iteration #2: (c3) (i3) (c1) (i1) (c2) (i2) (c0) (c4) (i4) (i0) (c5) (i5) (c7) (c8) (i7) (c9) (i9) (c6) (i6) (i8)
...
INFO: Iteration #913: (c0) (i0) (c2) (i2) (c3) (i3) (c4) (c5) (i4) (i5) (c1) (c6) (i1) (i6) (c7) (i7) (c8) (i8) (c9) (i9)
INFO: Iteration #914: (c0) (c1) (c3) (i3) (i0) (c2) (i2) (i1)
ERROR: On iteration #914 id #4, actualResultsFromProgram != expectedResultFromProgram, 4!=5
ERROR: On iteration #914 id #5, actualResultsFromProgram != expectedResultFromProgram, 5!=6
ERROR: On iteration #914 id #6, actualResultsFromProgram != expectedResultFromProgram, 6!=7
ERROR: On iteration #914 id #7, actualResultsFromProgram != expectedResultFromProgram, 7!=8
ERROR: On iteration #914 id #8, actualResultsFromProgram != expectedResultFromProgram, 8!=9
ERROR: On iteration #914 id #9, actualResultsFromProgram != expectedResultFromProgram, 9!=10
package minijava;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;
public class Programs {
public int species = 1;
private static final JavaCompiler JAVA_COMPILER = ToolProvider.getSystemJavaCompiler();
private static final int MAX_POPULATION = 10;
private List<Program> listProgram = new ArrayList<Program>(MAX_POPULATION);
private static final int AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
private long iteration = 0;
// The compute function will increment the first array value by one
private static final String sourceCodeTemplate =
"package species0.id0; \n" +
"import java.util.ArrayList; \n" +
"public class GeneticProgram { \n" +
" public static void compute(ArrayList<Long> values00) { \n" +
" long value = values00.get(0); \n" +
" System.out.print(\" (c\" + value + \") \"); \n" +
" values00.set(0, value + 1); \n" +
" } \n" +
"} \n";
public Programs() {
System.out.println(sourceCodeTemplate);
int errorCode = 0;
while(errorCode == 0) {
System.out.print("\nINFO: Iteration #" + iteration + ":");
errorCode = createPrograms();
if(errorCode == 0) {
compilePrograms();
}
if(errorCode == 0) {
executePrograms();
}
iteration++;
}
}
public int createPrograms() {
listProgram.clear();
for(int index=0; index<MAX_POPULATION; index++) {
String simulatedRandomSourceCode = replacePackage(sourceCodeTemplate, species, index);
Program program = new Program(simulatedRandomSourceCode, species, index);
program.vectors.add(new Long(index)); // this number will be incremented by 1 upon execution of program
listProgram.add(program);
}
return 0;
}
public int compilePrograms() {
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
for(Program program : listProgram) {
try (StandardJavaFileManager standardJavaFileManager = JAVA_COMPILER.getStandardFileManager(diagnostics, Locale.ENGLISH, null)) {
Iterable<Program> javaFileObject = Arrays.asList(program);
ProgramClassSimpleJavaFileObject programClassSimpleJavaFileObject = null;
try {
programClassSimpleJavaFileObject = new ProgramClassSimpleJavaFileObject(Program.PACKAGE_SPECIES + species + "." + Program.PACKAGE_ID + program.ID + "." + Program.PROGRAM_CLASS);
} catch (Exception e) {
e.printStackTrace();
return -1;
}
ProgramForwardingJavaFileManager programForwardingJavaFileManager = new ProgramForwardingJavaFileManager(standardJavaFileManager, programClassSimpleJavaFileObject, program.programClassLoader);
CompilationTask compilerTask = JAVA_COMPILER.getTask(null, programForwardingJavaFileManager, diagnostics, null, null, javaFileObject);
if (!compilerTask.call()) {
System.out.println("\nERROR: compilePrograms compilerTask.call()");
return -1;
}
} catch (IOException e) {
e.printStackTrace();
return -1;
}
}
return 0;
}
public int executePrograms() {
List<CallableMiniJava> listCallable = new ArrayList<CallableMiniJava>(MAX_POPULATION);
ExecutorService executorService = Executors.newFixedThreadPool(AVAILABLE_PROCESSORS);
try {
for(Program program : listProgram) {
listCallable.add(new CallableMiniJava(program));
}
for(CallableMiniJava callableMiniJava : listCallable) {
executorService.execute(callableMiniJava);
}
executorService.shutdown();
if(!executorService.awaitTermination(1000, TimeUnit.MILLISECONDS)) {
executorService.shutdownNow();
int milliseconds = 1000;
while(!executorService.awaitTermination(1000, TimeUnit.MILLISECONDS)) {
milliseconds += 1000;
System.out.println("\nINFO: Runaway program for " + milliseconds + " milliseconds");
}
}
for (Program program : listProgram) {
long actualResultsFromProgram = program.vectors.get(0);
long expectedResultFromProgram = program.ID + 1;
if(actualResultsFromProgram != expectedResultFromProgram) {
System.out.println("\nERROR: On iteration #" + iteration + " id #" + program.ID + ", actualResultsFromProgram != expectedResultFromProgram, " + actualResultsFromProgram + "!=" + expectedResultFromProgram);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
return -1;
}
return 0;
}
public static String replacePackage(String source, int species, int packageNumber) {
return source.replaceFirst("package species[0-9][^;]*;", "package " + Program.PACKAGE_SPECIES + species + "." + Program.PACKAGE_ID + packageNumber + ";");
}
public static void main(String[] args) {
Programs programs = new Programs();
}
}
package minijava;
import java.net.URI;
import java.util.ArrayList;
import javax.tools.SimpleJavaFileObject;
public class Program extends SimpleJavaFileObject {
public static final String PROGRAM_CLASS = new String("GeneticProgram");
public static final String PACKAGE_SPECIES = new String("species");
public static final String PACKAGE_ID = new String("id");
public String source;
public ArrayList<Long> vectors;
public int species;
public int ID;
public ProgramClassLoader programClassLoader = null;
Program(String source, int species, int ID) {
super(URI.create("string:///" + PACKAGE_SPECIES + species + '/' + PACKAGE_ID + ID + '/' + PROGRAM_CLASS + Kind.SOURCE.extension), Kind.SOURCE);
this.source = new String(source);
this.species = species;
this.ID = ID;
vectors = new ArrayList<Long>(1);
programClassLoader = new ProgramClassLoader(ClassLoader.getSystemClassLoader());
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return source;
}
}
package minijava;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class CallableMiniJava implements Runnable {
private Program program = null;
private Class<?> cls = null;
private Method method = null;
public CallableMiniJava(Program program) {
if(program.vectors != null) {
this.program = program;
try {
cls = program.programClassLoader.loadClass(Program.PACKAGE_SPECIES + program.species + "." + Program.PACKAGE_ID + program.ID + "." + Program.PROGRAM_CLASS);
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
}
try {
method = cls.getMethod("compute", ArrayList.class);
} catch (NoSuchMethodException e1) {
e1.printStackTrace();
} catch (SecurityException e1) {
e1.printStackTrace();
}
}
}
@Override
public void run() {
try {
method.invoke(null, program.vectors);
System.out.print(" (i" + program.ID + ") ");
} catch(Exception e) {
e.printStackTrace();
}
}
}
package minijava;
import java.util.HashMap;
import java.util.Map;
public class ProgramClassLoader extends ClassLoader {
public Map<String, ProgramClassSimpleJavaFileObject> mapProgramClass = new HashMap<>();
public ProgramClassLoader(ClassLoader classLoader) {
super(classLoader);
}
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
ProgramClassSimpleJavaFileObject programClassSimpleJavaFileObject = mapProgramClass.get(className);
if(programClassSimpleJavaFileObject != null) {
byte[] byteCode = programClassSimpleJavaFileObject.byteArrayOutputStream.toByteArray();
return defineClass(className, byteCode, 0, byteCode.length);
} else {
return super.findClass(className);
}
}
}
package minijava;
import javax.tools.SimpleJavaFileObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
public class ProgramClassSimpleJavaFileObject extends SimpleJavaFileObject {
public ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
public ProgramClassSimpleJavaFileObject(String className) throws Exception {
super(new URI(className), Kind.CLASS);
}
@Override
public OutputStream openOutputStream() throws IOException {
return byteArrayOutputStream;
}
}
package minijava;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import java.io.IOException;
public class ProgramForwardingJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
private ProgramClassLoader programClassLoader;
private ProgramClassSimpleJavaFileObject programClassSimpleJavaFileObject;
protected ProgramForwardingJavaFileManager(JavaFileManager javaFileManager, ProgramClassSimpleJavaFileObject programClassSimpleJavaFileObject, ProgramClassLoader programClassLoader) {
super(javaFileManager);
this.programClassSimpleJavaFileObject = programClassSimpleJavaFileObject;
this.programClassLoader = programClassLoader;
this.programClassLoader.mapProgramClass.put(programClassSimpleJavaFileObject.getName(), programClassSimpleJavaFileObject);
}
@Override
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, Kind kind, FileObject fileObject) throws IOException {
return programClassSimpleJavaFileObject;
}
@Override
public ClassLoader getClassLoader(Location location) {
return programClassLoader;
}
}
共 (0) 个答案