gpt4 book ai didi

Java动态加载和卸载.java文件,垃圾收集?

转载 作者:IT王子 更新时间:2023-10-28 23:31:52 25 4
gpt4 key购买 nike

我正在创建一个可以长时间运行的 Java 应用程序,该应用程序需要更新功能而无需关闭。我决定通过在内存中编译并实例化的 .java 文件(作为字节数组从数据库中提取)的形式加载它来提供这个更新的功能。如果你有更好的方法,我会全力以赴。

我遇到的问题是,当我在人工环境中进行一些测试时,每次加载这些“脚本”循环时,内存占用都会略有增加。

注意:这实际上是我第一次用 java 做这样的事情或很多事情。我之前在 C# 中通过加载和卸载 .cs 文件完成了类似的事情,并且在那里也遇到了内存占用问题......为了解决我将它们加载到单独的应用程序域中,当我重新编译文件时,我刚刚卸载了该应用程序域并创建了一个新的。

入口点


这是我用来模拟长时间使用(多次重新编译循环)后的内存占用的入口方法。我运行了一小段时间,它很快就吃光了 500MB 以上。

这仅在临时目录中有两个虚拟脚本。

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类


这是我的多用途 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;
}
}

脚本界面


最后是简单的脚本界面。

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

在编程方面我还是个新手,我已经使用堆栈一段时间来找到一些解决我遇到的小问题的方法,这是我第一次提出问题,如果我包括在内,我深表歉意信息太多或太长;我只是想确保我是彻底的。

最佳答案

您似乎正在使用应用程序的默认类加载器来加载已编译的类 - 这使得这些类不可能被垃圾收集。

所以你要create a separate classloader为您新编译的类(class)。这就是应用服务器的工作方式。

但是,即使您为编译的类使用单独的类加载器,让这些类被垃圾回收拾取也是很棘手的,因为类加载器和它加载的所有类只要不符合垃圾回收的条件因为这些类中的任何一个的单个实例在其他任何地方(即应用程序的其余部分)都被引用。

这被称为 classloader leak以及应用服务器的一个常见问题,导致重新部署使用更多内存并最终失败。诊断和修复类加载器泄漏可能非常棘手。这篇文章有所有的细节。

关于Java动态加载和卸载.java文件,垃圾收集?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9889793/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com