我正在创建一个将长时间运行的java应用程序,这需要更新的功能而不需要关闭。我决定通过以.java文件的形式加载它(作为数据库中的字节数组)来提供这种更新的功能,这些文件在内存中编译并实例化。如果你有一个更好的方式,我全是耳朵。Java动态加载和卸载.java文件,垃圾收集?
我在跑的问题是,内存占用与当我做在人工环境中一些测试加载这些“脚本”的每个周期略有增加。
注意:这实际上是我第一次做这样的事情,或者根本就不用java。我之前在C#中使用加载和卸载.cs文件完成了这样的事情,并且在那里还有内存占用问题......为了解决这个问题,我将它们加载到单独的AppDomain中,当我重新编译文件时,我只卸载了该AppDomain并创建了一个新的一个。
切入点
这是输入法,我用模拟使用(很多重新编译循环)经过长时间的内存占用。我运行这个很短的时间,它很快就吃了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文件,并将其实例化到脚本对象的实际方法。
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;
}
我仍然是一种新的,当涉及到编程,我已经使用的堆栈一会儿就找到我所遇到的小问题了一些解决方案,这是我第一次问一个问题,所以我道歉如果我包含了太多的信息或者这太长了,我只是想确保我彻底。
你如何测量内存占用?在Java中,程序实际使用*的内存量有多大,以及它可以使用多少内存*只是因为它可以。 – 2012-03-27 12:58:26
对于一个精心布置的问题+1,我有兴趣看到这个答案。你有机会看过Java反射吗? – FloppyDisk 2012-03-27 13:00:08
我在使用Eclipse Memory Analyzer注意到我的任务管理器中特定javaw.exe进程的内存使用增加。看起来好像垃圾收集器没有做任何事情来收集未使用的残余物......还有一个事实是,如果我删除睡眠并将其设置为while(true),则由于内存不足而崩溃。 – Jordan 2012-03-27 13:04:25