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

时间:2014-10-11 00:20:54

标签: java

我创建了一个类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次编译我会重新启动服务器或其他东西(但它很乱)

2 个答案:

答案 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