Clean up class loaded via .forName class in java

I have created a program that downloads a java (JPanel) file that the user selects. The user basically selects a Java file that JavaCompiler compiles and the next generated class file is loaded. But the problem occurs when any changes are made in the java file (JPanel) through some text editor, as any new changes are not reflected in the class file even after the program is closed and the project is re-run.

I think the same class file is loaded over and over from memory.

Is there a way to clear the loaded class from memory?

Compilation:

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler != null) {
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
        StandardJavaFileManager stdFileManager = compiler.getStandardFileManager(diagnostics, null, null);
        Iterable<? extends JavaFileObject> fileObjects = stdFileManager.getJavaFileObjectsFromFiles(filesToCompile);
        List<String> optionList = new ArrayList<String>();
        // set compiler classpath to be same as the runtime's
        rootDir=Utility.createRootDir();
        optionList.addAll(Arrays.asList("-d", rootDir.getAbsolutePath(), "-classpath", System.getProperty("java.class.path")));
        // optionList.add(()
        try {
            stdFileManager.flush();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        CompilationTask task = compiler.getTask(null, stdFileManager,null, optionList, null, fileObjects);
        Boolean result = task.call();
        try {
            stdFileManager.flush();
            stdFileManager.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
}

      

Loading:

loader = new URLClassLoader(new URL[] { rootDir.toURI().toURL() });
cls = Class.forName(Utility.extractFQDN(sourceFile)+"."+Utility.extractClassName(sourceFile),true, loader); 

      

panel= (JPanel) cls.newInstance();

I checked the compiled class file with decompiler, it updated the code, but I don't know why the previous class file is loaded from memory by the class loader.

+3


source to share


1 answer


Edit:

Here SSCCE compiles strings sequentially for the same class name and demonstrates new behavior. To avoid messing with files, it does everything in memory. I think this should adapt easily to your application.

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;


public class Compile {
    static class OutFile extends SimpleJavaFileObject {
        private final ByteArrayOutputStream out = new ByteArrayOutputStream();

        OutFile(String name) {
            super(URI.create("memory:///" + name.replace('.','/') + Kind.CLASS.extension), Kind.CLASS);
        }

        @Override
        public OutputStream openOutputStream() throws IOException {
            return out;
        }
    }

    static class InFile extends SimpleJavaFileObject {
        final String code;

        InFile(String name, String code) {
            super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension), Kind.SOURCE);
            this.code = code;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return code;
        }
    }

    static class Loader extends ClassLoader {
        private final Map<String, OutFile> files = new HashMap<String, OutFile>();

        public OutFile add(String className) {
            OutFile file = new OutFile(className);
            files.put(className, file);
            return file;
        }

        @Override
        protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            Class<?> c = findLoadedClass(name);
            if(c == null) {
                OutFile file = files.get(name);
                if(file == null) {
                    return super.loadClass(name, resolve);
                }

                c = defineClass(name, file.out.toByteArray(), 0, file.out.size());
            }

            if(resolve) {
                resolveClass(c);
            }

            return c;
        }
    }

    static class FileManager extends ForwardingJavaFileManager<JavaFileManager> {
        private final Loader loader = new Loader();

        protected FileManager(JavaFileManager fileManager) {
            super(fileManager);
        }

        public Class<?> getClass(String name) throws ClassNotFoundException {
            return loader.loadClass(name);
        }

        @Override
        public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
            return loader.add(className);
        }
    }

    public static void compileAndRun(String source) throws Exception {
        InFile in = new InFile("Main", "class Main {\npublic static void main(String[] args) {\n" + source + "\n}\n}");

        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        FileManager manager = new FileManager(compiler.getStandardFileManager(null, null, null));

        compiler.getTask(null, manager, null, null, null, Collections.singletonList(in)).call();

        Method method = manager.getClass("Main").getMethod("main", String[].class);
        method.setAccessible(true);
        method.invoke(null, (Object)new String[0]);
    }

    public static void main(String[] args) throws Exception {
        compileAndRun("System.out.println(\"Hello\");");
        compileAndRun("System.out.println(\"World\");");
    }
}

      

Original:



ClassLoader

(and type subclasses URLClassLoader

) will always ask the parent class loader to load the class if there is a parent. If you don't explicitly set the parent when you create it, the parent is set to the system class loader. This way, any new classloaders you create are deferred back to a system class loader that already has a specific class.

To get the behavior you want, set the parent element to null:

loader = new URLClassLoader(new URL[] { rootDir.toURI().toURL() }, null);

      

Edit: Please note that this is only a problem because you are compiling your classes to the root directory, which is also in the classpath of the system class loader. If you are compiled into some temp directory the system class loader will not be able to find the class and the url loader will load the class itself.

+1


source







All Articles