我创建了一个类JavaRunner
,它从字符串动态创建一个文件,在内存中编译它并运行它的main方法(我还创建了一个写入文件并将其编译到磁盘上的方法,结果类似)。 / p>
我创建了另外两个调用跑步者的类。
第一个是TerminalRunner
,它将类名和源作为参数并调用JavaRunner.compile,这样可以正常工作,因为每次调用它时它只会运行一次。
第二个类是RunnerServlet
,它启动一个小的java服务器,它接收一个使用JavaRunner编译的post请求并运行代码并返回一个带有sys.out和sys.err流的JSON对象。
如果我发布{name:“Main”,代码:“[Some Java code]”}我得到正确的回复;但是如果我使用不同的源代码调用同一个Main类,我会得到第一个结果。
我跟踪代码,源String正确地传递给JavaCompiler。 问题与编译的类有关,我猜它是由JVM以某种方式缓存的。
这是JavaRunner.java
public static void compile(String name, String code, int timeLimit){
/*Creating dynamic java source code file object*/
SimpleJavaFileObject fileObject = new DynamicJavaSourceCodeObject (name, code) ;
JavaFileObject javaFileObjects[] = new JavaFileObject[]{fileObject} ;
/*Instantiating the java compiler*/
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
/**
* Retrieving the standard file manager from compiler object, which is used to provide
* basic building block for customizing how a compiler reads and writes to files.
*
* The same file manager can be reopened for another compiler task.
* Thus we reduce the overhead of scanning through file system and jar files each time
*/
StandardJavaFileManager stdFileManager = compiler.getStandardFileManager(null, null, null);
try {
stdFileManager.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(new File("./temp")));
} catch (IOException e) {
e.printStackTrace();
}
/* Prepare a list of compilation units (java source code file objects) to input to compilation task*/
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(javaFileObjects);
/*Prepare any compilation options to be used during compilation*/
//In this example, we are asking the compiler to place the output files under bin folder.
List<String> compileOptions = new ArrayList<String>();
// compileOptions.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path")));
// Iterable<String> compilationOptionss = Arrays.asList(compileOptions);
/*Create a diagnostic controller, which holds the compilation problems*/
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
/*Create a compilation task from compiler by passing in the required input objects prepared above*/
CompilationTask compilerTask = compiler.getTask(null, stdFileManager, diagnostics, compileOptions, null, compilationUnits) ;
//Perform the compilation by calling the call method on compilerTask object.
boolean status = compilerTask.call();
if (!status){//If compilation error occurs
/*Iterate through each compilation problem and print it*/
for (Diagnostic diagnostic : diagnostics.getDiagnostics()){
System.err.format("Error on line %d in %s", diagnostic.getLineNumber(), diagnostic);
}
} else {
// ExecutorService service = Executors.newSingleThreadExecutor();
// try {
// Runnable r = new Runnable() {
// @Override
// public void run() {
try {
Class.forName(name).getDeclaredMethod("main", new Class[] { String[].class }).invoke(null, new Object[] { null });
} catch (ClassNotFoundException e) {
System.err.println("Class not found: " + e);
} catch (NoSuchMethodException e) {
System.err.println("No such method: " + e);
} catch (IllegalAccessException e) {
System.err.println("Illegal access: " + e);
} catch (InvocationTargetException e) {
System.err.println("RuntimeError: "+e.getTargetException());
}
// }
// };
// Future<?> f = service.submit(r);
// f.get(timeLimit, TimeUnit.MILLISECONDS); // attempt the task for timelimit default 5 seconds
// }
// catch (final InterruptedException e) {
// System.err.println("Thread Interrupted: " + e);
// }
// catch (final TimeoutException e) {
// System.err.println("TimeoutException: Your program ran for more than "+timeLimit);
// }
// catch (final ExecutionException e) {
// e.printStackTrace();
// }
// finally {
// service.shutdown();
// }
}
try {
(new File("./temp/"+name+".class")).delete();
stdFileManager.close() ;//Close the file manager
} catch (IOException e) {
e.printStackTrace();
}
}
这是DynaDynamicJavaSourceCodeObject
class DynamicJavaSourceCodeObject extends SimpleJavaFileObject{
private String sourceCode ;
/**
* Converts the name to an URI, as that is the format expected by JavaFileObject
*
*
* @param String name given to the class file
* @param String source the source code string
*/
protected DynamicJavaSourceCodeObject(String name, String source) {
super(URI.create("string:///" +name.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
this.sourceCode = source ;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors)
throws IOException {
return sourceCode ;
}
public String getSourceCode() {
return sourceCode;
}
}
有什么建议吗?
到目前为止,我将CLASS_OUPUT设置为/temp
目录,我将其删除
然而,一旦一个类被定义,即使在我删除它之后,它仍留在内存中
有没有办法从java的内存中清除类?
我使用当前进度here
创建了一个回购我的解决方法,如果所有其他方法都失败了,那就是生成随机文件名,然后每10000次编译我会重新启动服务器或其他东西(但它很乱)
答案 0 :(得分:3)
感谢@ pm-77-1的建议和评论中的热门舔
我使用了SecureClassLoader
类并使其编译,以便在那里加载编译的字节码
这是完整的课程
public class JavaRunner {
public static void compile(String name, String code){
compile(name,code,5000);
}
/**
* compiles and runs main method from code
* @param name Class Name
* @param code String to compile
* @param timeLimit (otional) limit for code to run, default to 5 seconds
*/
public static void compile(String name, String code, int timeLimit){
/*Creating dynamic java source code file object*/
SimpleJavaFileObject fileObject = new DynamicJavaSourceCodeObject (name, code) ;
JavaFileObject javaFileObjects[] = new JavaFileObject[]{fileObject} ;
/*Instantiating the java compiler*/
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
/**
* Retrieving the standard file manager from compiler object, which is used to provide
* basic building block for customizing how a compiler reads and writes to files.
*
* The same file manager can be reopened for another compiler task.
* Thus we reduce the overhead of scanning through file system and jar files each time
*/
StandardJavaFileManager stdFileManager = compiler.getStandardFileManager(null, null, null);
//uses custom file manager with defined class loader inorder to unload the compiled class when this is done
ClassFileManager fileManager = new ClassFileManager(stdFileManager);
/* Prepare a list of compilation units (java source code file objects) to input to compilation task*/
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(javaFileObjects);
/*Prepare any compilation options to be used during compilation*/
//In this example, we are asking the compiler to place the output files under bin folder.
List<String> compileOptions = new ArrayList<String>();
// compileOptions.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path")));
// Iterable<String> compilationOptionss = Arrays.asList(compileOptions);
/*Create a diagnostic controller, which holds the compilation problems*/
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
/*Create a compilation task from compiler by passing in the required input objects prepared above*/
CompilationTask compilerTask = compiler.getTask(null, fileManager, diagnostics, compileOptions, null, compilationUnits) ;
//Perform the compilation by calling the call method on compilerTask object.
boolean status = compilerTask.call();
if (!status){//If compilation error occurs
/*Iterate through each compilation problem and print it*/
for (Diagnostic diagnostic : diagnostics.getDiagnostics()){
System.err.format("Error on line %d in %s", diagnostic.getLineNumber(), diagnostic);
}
} else {
ExecutorService service = Executors.newSingleThreadExecutor();
try {
Runnable r = new Runnable() {
@Override
public void run() {
try {
fileManager.getClassLoader(null).loadClass(name).getDeclaredMethod("main", new Class[] { String[].class }).invoke(null, new Object[] { null });
} catch (ClassNotFoundException e) {
System.err.println("Class not found: " + e);
} catch (NoSuchMethodException e) {
System.err.println("No such method: " + e);
} catch (IllegalAccessException e) {
System.err.println("Illegal access: " + e);
} catch (InvocationTargetException e) {
System.err.println("RuntimeError: "+e.getTargetException());
}
try {
fileObject.delete();
fileManager.close();
ResourceBundle.clearCache(ClassLoader.getSystemClassLoader()); // <--useless
} catch (IOException e) {
e.printStackTrace();
}
}
};
Future<?> f = service.submit(r);
f.get(timeLimit, TimeUnit.MILLISECONDS);
}
catch (final InterruptedException e) {
System.err.println("Thread Interrupted: " + e);
}
catch (final TimeoutException e) {
System.err.println("TimeoutException: Your program ran for more than "+timeLimit);
}
catch (final ExecutionException e) {
e.printStackTrace();
}
finally {
service.shutdown();
}
}
}
}
这为编译准备了动态java源代码。
class DynamicJavaSourceCodeObject extends SimpleJavaFileObject{
private String sourceCode ;
/**
* Converts the name to an URI, as that is the format expected by JavaFileObject
*
*
* @param String name given to the class file
* @param String source the source code string
*/
protected DynamicJavaSourceCodeObject(String name, String source) {
super(URI.create("string:///" +name.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
this.sourceCode = source ;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors)
throws IOException {
return sourceCode ;
}
public String getSourceCode() {
return sourceCode;
}
}
这个想法是创建一个Dynamic类而不是写入文件
class JavaClassObject extends SimpleJavaFileObject {
/**
* Byte code created by the compiler will be stored in this
* ByteArrayOutputStream so that we can later get the
* byte array out of it
* and put it in the memory as an instance of our class.
*/
protected ByteArrayOutputStream bos =
new ByteArrayOutputStream();
/**
* Registers the compiled class object under URI
* containing the class full name
*
* @param name
* Full name of the compiled class
* @param kind
* Kind of the data. It will be CLASS in our case
*/
public JavaClassObject(String name, Kind kind) {
super(URI.create("string:///" + name.replace('.', '/')
+ kind.extension), kind);
}
/**
* Will be used by our file manager to get the byte code that
* can be put into memory to instantiate our class
*
* @return compiled byte code
*/
public byte[] getBytes() {
return bos.toByteArray();
}
/**
* Will provide the compiler with an output stream that leads
* to our byte array. This way the compiler will write everything
* into the byte array that we will instantiate later
*/
@Override
public OutputStream openOutputStream() throws IOException {
return bos;
}
}
我们使用这个文件管理器,以便可以卸载源代码中的已编译类,也不必写入文件系统
class ClassFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
/**
* Instance of JavaClassObject that will store the
* compiled bytecode of our class
*/
private JavaClassObject jclassObject;
/**
* Instance of ClassLoader
*/
private SecureClassLoader classLoader;
/**
* Will initialize the manager with the specified
* standard java file manager
*
* @param standardManger
*/
public ClassFileManager(StandardJavaFileManager standardManager) {
super(standardManager);
this.classLoader = new SecureClassLoader() {
@Override
protected Class<?> findClass(String name)
throws ClassNotFoundException {
byte[] b = jclassObject.getBytes();
return super.defineClass(name, jclassObject
.getBytes(), 0, b.length);
}
};
}
/**
* Will be used by us to get the class loader for our
* compiled class. It creates an anonymous class
* extending the SecureClassLoader which uses the
* byte code created by the compiler and stored in
* the JavaClassObject, and returns the Class for it
*/
@Override
public ClassLoader getClassLoader(Location location) {
return this.classLoader;
}
public void unloadClass(Location location) {
this.classLoader = null;
this.jclassObject = null;
System.gc();
}
/**
* Gives the compiler an instance of the JavaClassObject
* so that the compiler can write the byte code into it.
*/
@Override
public JavaFileObject getJavaFileForOutput(Location location,
String className, Kind kind, FileObject sibling)
throws IOException {
jclassObject = new JavaClassObject(className, kind);
return jclassObject;
}
}
答案 1 :(得分:2)
我认为Class<?>
对象被垃圾收集的唯一方法是对关联的ClassLoader
进行垃圾回收,如果没有更多的引用,这只能被收集。 ClassLoader
以及通过此ClassLoader
加载的任何类。有关详细信息,请查看this question。