Java cargando y descargando archivos .java dinámicamente, recolección de basura?

Estoy en el proceso de crear una aplicación Java que se ejecutará durante largos períodos de tiempo que requiere una funcionalidad actualizada sin cerrar. He decidido proporcionar esta funcionalidad actualizada cargándola en forma de archivos .java (extraídos como una matriz de bytes de una base de datos) que se compilan en la memoria y se instancian. Si tienes una mejor manera, soy todo oídos.

El problema con el que me he encontrado es que la huella de la memoria aumenta ligeramente con cada ciclo de carga de estos "scripts" cuando hago algunas pruebas en un entorno artificial.

Nota Esta es la primera vez que hago algo así o mucho con Java. Había logrado algo como esto antes en C # con la carga y descarga de archivos .cs y también tuve problemas de huella de memoria allí ... para resolver que los cargué en un dominio de aplicación separado y cuando volví a compilar los archivos, simplemente descargué ese dominio de aplicación y creé un uno nuevo

Punto de entrad

Este es el método de entrada que estoy usando para simular la huella de memoria después de largos períodos de uso (muchos ciclos de recompilación). Ejecuto esto por un corto período de tiempo y rápidamente consume 500MB +.

Esto es solo con dos scripts ficticios en el directorio temporal.

public static void main( String[ ] args ) throws Exception {
    for ( int i = 0; i < 1000; i++ ) {
        Container[ ] containers = getScriptContainers( );
        Script[ ] scripts = compileScripts( containers );

        for ( Script s : scripts ) s.Begin( );
        Thread.sleep( 1000 );
    }
}
Recolección de una lista de scripts (temporales)

Este es el método temporal que estoy utilizando para recopilar una lista de los archivos de script. Durante la producción, estos se cargarán como conjuntos de bytes con alguna otra información, como el nombre de la clase de una base de datos.

@Deprecated
private static Container[ ] getScriptContainers( ) throws IOException {
    File root = new File( "C:\\Scripts\\" );
    File[ ] files = root.listFiles( );

    List< Container > containers = new ArrayList<>( );
    for ( File f : files ) {
        String[ ] tokens = f.getName( ).split( "\\.(?=[^\\.]+$)" );
        if ( f.isFile( ) && tokens[ 1 ].equals( "java" ) ) {
            byte[ ] fileBytes = Files.readAllBytes( Paths.get( f.getAbsolutePath( ) ) );
            containers.add( new Container( tokens[ 0 ], fileBytes ) );
        }
    }

    return containers.toArray( new Container[ 0 ] );
 }
Container class

Esta es la clase de contenedor simple.

public class Container {
    private String className;
    private byte[ ] classFile;

    public Container( String name, byte[ ] file ) {
        className = name;
        classFile = file;
    }

    public String getClassName( ) {
        return className;
    }

    public byte[ ] getClassFile( ) {
        return classFile;
    }
}
Compilación de los scripts

Este es el método real que compila los archivos .java y los instancia en objetos Script.

private static Script[ ] compileScripts( Container[ ] containers ) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    List< ClassFile > sourceScripts = new ArrayList<>( );
    for ( Container c : containers )
        sourceScripts.add( new ClassFile( c.getClassName( ), c.getClassFile( ) ) );

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

    compiler.getTask( null, manager, null, null, null, sourceScripts ).call( );

    List< Script > compiledScripts = new ArrayList<>( );
    for ( Container c : containers )
        compiledScripts.add( ( Script )manager.getClassLoader( null ).loadClass( c.getClassName( ) ).newInstance( ) );

    return ( Script[ ] )compiledScripts.toArray( new Script[ 0 ] );
}
MemoryFileManager class

Esta es la @ personaliza JavaFileManager implementación que creé para el compilador para poder almacenar la salida en la memoria en lugar de en archivos .class físicos.

public class MemoryFileManager extends ForwardingJavaFileManager< JavaFileManager > {
    private HashMap< String, ClassFile > classes = new HashMap<>( );

    public MemoryFileManager( StandardJavaFileManager standardManager ) {
        super( standardManager );
    }

    @Override
    public ClassLoader getClassLoader( Location location ) {
        return new SecureClassLoader( ) {
            @Override
            protected Class< ? > findClass( String className ) throws ClassNotFoundException {
                if ( classes.containsKey( className ) ) {
                    byte[ ] classFile = classes.get( className ).getClassBytes( );
                    return super.defineClass( className, classFile, 0, classFile.length );
                } else throw new ClassNotFoundException( );
            }
        };
    }

    @Override
    public ClassFile getJavaFileForOutput( Location location, String className, Kind kind, FileObject sibling ) {
        if ( classes.containsKey( className ) ) return classes.get( className );
        else {
            ClassFile classObject = new ClassFile( className, kind );
            classes.put( className, classObject );
            return classObject;
        }
    }
}
ClassFile class

Este es mi @ multiu SimpleJavaFileObject implementación que uso para almacenar los archivos .java de origen y los archivos .class compilados en la memoria.

public class ClassFile extends SimpleJavaFileObject {
    private byte[ ] source;
    protected final ByteArrayOutputStream compiled = new ByteArrayOutputStream( );

    public ClassFile( ,String className, byte[ ] contentBytes ) {
        super( URI.create( "string:///" + className.replace( '.', '/' ) + Kind.SOURCE.extension ), Kind.SOURCE );
        source = contentBytes;
    }

    public ClassFile( String className, CharSequence contentCharSequence ) throws UnsupportedEncodingException {
        super( URI.create( "string:///" + className.replace( '.', '/' ) + Kind.SOURCE.extension ), Kind.SOURCE );
        source = ( ( String )contentCharSequence ).getBytes( "UTF-8" );
    }

    public ClassFile( String className, Kind kind ) {
        super( URI.create( "string:///" + className.replace( '.', '/' ) + kind.extension ), kind );
    }

    public byte[ ] getClassBytes( ) {
        return compiled.toByteArray( );
    }

    public byte[ ] getSourceBytes( ) {
        return source;
    }

    @Override
    public CharSequence getCharContent( boolean ignoreEncodingErrors ) throws UnsupportedEncodingException {
        return new String( source, "UTF-8" );
    }

    @Override
    public OutputStream openOutputStream( ) {
        return compiled;
    }
}
nterfaz @Script

Y por último el simpleGuió interfaz

public interface Script {
    public void Begin( ) throws Exception;
}

Todavía soy un poco nuevo en lo que respecta a la programación y he usado la pila durante un tiempo para encontrar algunas soluciones a los pequeños problemas que he encontrado, esta es la primera vez que hago una pregunta, así que me disculpo si he incluido demasiado información o si es demasiado larga; Solo quería asegurarme de que fuera minucioso.

Respuestas a la pregunta(2)

Su respuesta a la pregunta