2014-10-11 93 views
4

我創建了一個類JavaRunner,它從字符串動態地創建一個文件,將其編譯到內存中並運行它的主要方法(我還創建了一個寫入文件並將其編譯到磁盤上的方法結果相似)。如何從內存中清除動態編譯的類

我創建了2個其他類,稱爲跑步者。

第一個是TerminalRunner,它將類名和源作爲參數並調用JavaRunner.compile,這很好,因爲它每次調用它時只運行一次。

第二類是RunnerServlet,它啓動一個小的java服務器,它接收一個使用JavaRunner編寫的post請求並運行代碼並返回一個帶有sys.out和sys.err流的JSON對象。

如果我發佈{name:「Main」,代碼:「[某些Java代碼]」}我得到正確的響應;然而,如果我用不同的源代碼調用同一個Main類,我會得到第一個結果。

我追蹤了代碼,並將源字符串正確傳遞給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(); 
    } 
} 

compile方法這是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彙編我會重新啓動服務器或東西(但它是凌亂)

+3

請參閱[在java中卸載類?](http://stackoverflow.com/questions/148681/unloading-classes-in-java)瞭解一些選項。 – 2014-10-11 00:24:39

+0

似乎很有趣 – 2014-10-11 00:26:18

+2

您必須創建一個類加載器,使用該類加載器加載該類,然後在完成後清空所有對類和類加載器的引用。 (包括引用類的任何類,請注意,這意味着你必須使用「工廠」來創建類的實例。) – 2014-10-11 00:44:33

回答

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; 
} 
} 

的想法是創建的,而不是寫入文件

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; 
} 
} 
2

我覺得對於被垃圾收集Class<?>對象的唯一途徑,是相關ClassLoader進行垃圾回收,這對如果沒有更多引用ClassLoader以及通過此ClassLoader加載的任何類,那麼它們就有資格收集。有關更多信息,請參閱this question

+1

是的,重要的一點是必須有* no *引用。並且不得有其他引用,例如,來自另一個類加載器的「父」引用。 – 2014-10-11 21:16:13