Подтвердить что ты не робот

Java загружает и выгружает .java файлы динамически, сбор мусора?

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

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

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

Точка входа


Это метод ввода, который я использую для имитации объема памяти после длительных периодов использования (много циклов перекомпиляции). Я запускаю это в течение короткого периода времени, и он быстро съедает 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 );
    }
}

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


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

@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


Это моя многоцелевая реализация 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 интерфейс


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

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

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

4b9b3361

Ответ 1

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

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

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

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