Java загрузка и выгрузка .java файлов динамически, сборка мусора? - PullRequest
12 голосов
/ 27 марта 2012

Я нахожусь в процессе создания Java-приложения, которое будет работать в течение длительного времени, что требует обновления функциональных возможностей без выключения. Я решил предоставить эту обновленную функциональность, загрузив ее в виде файлов .java (извлекаемых как байтовый массив из базы данных), которые компилируются в памяти и создаются. Если у тебя есть способ получше, я весь в ушах.

Проблема, с которой я столкнулся, заключается в том, что объем памяти немного увеличивается с каждым циклом загрузки этих «сценариев», когда я выполняю тестирование в искусственной среде.

Примечание: На самом деле я впервые делаю что-то подобное или вообще много с java. Я сделал что-то подобное раньше в C # с загрузкой и выгрузкой файлов .cs, а также имел проблемы с занимаемой памятью ... чтобы решить, что я загрузил их в отдельный домен приложения, а когда перекомпилировал файлы, я просто выгрузил этот домен приложения и создал новый.

Точка входа


Это метод ввода, который я использую для имитации объема памяти после длительных периодов использования (много циклов перекомпиляции). Я запускаю это в течение короткого периода времени, и оно быстро съедает 500 МБ +.

Это только с двумя фиктивными скриптами во временном каталоге.

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

Сбор списка скриптов (временный)


Это временный метод, который я использую для сбора списка файлов сценариев. Во время производства они будут фактически загружаться как байтовые массивы с некоторой другой информацией, такой как имя класса, из базы данных.

@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 ] );
 }

Контейнерный класс


Это простой контейнерный класс.

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

Компиляция скриптов


Это фактический метод, который компилирует файлы .java и создает их экземпляры в объекты 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


Это пользовательская реализация JavaFileManager , которую я создал для компилятора, чтобы я мог сохранять выходные данные в памяти, а не в физических файлах .class.

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


Это моя многоцелевая SimpleJavaFileObject реализация, которую я использую для хранения исходных файлов .java и скомпилированных файлов .class в памяти.

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

Скрипт интерфейса


И, наконец, простой интерфейс Script .

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

Я все еще новичок, когда дело доходит до программирования, и некоторое время я использовал стек, чтобы найти решения для небольших проблем, с которыми я столкнулся, я впервые задаю вопрос, поэтому я прошу прощения, если я включил слишком много информации или если это слишком долго; Я просто хотел убедиться, что я был тщательным.

1 Ответ

5 голосов
/ 27 марта 2012

Вы, похоже, используете загрузчик классов по умолчанию для загрузки скомпилированных классов, что делает невозможным сбор мусора для классов.

Поэтому вам нужно создать отдельный загрузчик классов для ваших недавно скомпилированных классов.Вот как это делают серверы приложений.

Однако, даже если вы используете отдельный загрузчик классов для своих скомпилированных классов, может быть сложно получить эти классы для сбора мусора, потому что загрузчик классови все загруженные им классы не имеют права на сборку мусора, пока один экземпляр любого из этих классов упоминается где-либо еще (т. е. остальная часть вашего приложения).

Это известно как утечка загрузчика классов и распространенная проблема с серверами приложений, приводящая к тому, что повторные развертывания используют все больше памяти и в конечном итоге дают сбой.Диагностика и устранение утечек из загрузчика классов может быть очень сложной задачей;в статье есть все детали.

...