如何从内存中清除动态编译的类

Amr*_*raz 5 java

我创建了JavaRunner一个从字符串动态创建文件,在内存中编译文件并运行它的main方法的类(我还创建了一种方法来写入文件并将其编译到磁盘上,结果相似)。

我创建了另外两个叫跑步者的类。

第一个是TerminalRunner使用类名和源作为参数并调用JavaRunner.compile,因为它每次调用它仅运行一次,所以可以正常工作。

第二类是RunnerServlet启动一个小型Java服务器,该服务器接收一个发布请求,并使用JavaRunner进行编译,然后运行代码并返回带有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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是 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;
 }
}
Run Code Online (Sandbox Code Playgroud)

有什么建议吗?

到目前为止,我将CLASS_OUPUT设置为/temp删除它们的目录,但是一旦定义了一个类,即使我删除了它,它也会保留在内存中的某个位置

有没有办法从Java的内存中清除类?

我在这里用当前进度创建了一个仓库

如果所有其他方法都失败,我的解决方法是生成随机文件名,那么每进行10000次编译,我都会重新启动服务器或其他操作(但是很混乱)

Amr*_*raz 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();
      }
    }        
}
}
Run Code Online (Sandbox Code Playgroud)

这将为编译准备动态 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;
}
}
Run Code Online (Sandbox Code Playgroud)

这个想法是创建一个动态类而不是写入文件

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;
}
}
Run Code Online (Sandbox Code Playgroud)

我们使用这个文件管理器,以便可以卸载源代码中编译的类,而不必写入文件系统

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;
 }
}
Run Code Online (Sandbox Code Playgroud)