2012-03-27 22 views
12

私はシャットダウンせずに更新された機能を必要とする長期間実行されるJavaアプリケーションを作成中です。私はこの更新された機能をメモリにコンパイルされ、インスタンス化された.javaファイル(データベースからバイト配列として引っ張られる)の形でロードすることで提供することに決めました。あなたがより良い方法を持っているなら、私はすべての耳です。Javaの.javaファイルの動的ロード、ガベージコレクション

私が実行した問題は、人工的な環境でいくつかのテストを行うと、これらのスクリプトを読み込むたびにメモリの占有量がわずかに増加することです。

注:これは実際にはこのようなことをやっています。私はこの前にC#でロードとアンロードの.csファイルを使用していましたが、そこにメモリフットプリントの問題がありました...解決するために、別のappdomainにロードして、ファイルを再コンパイルしたところ、新しいもの

エントリポイント


これは私が使用の長時間(多くの再コンパイルサイクル)後のメモリフットプリントをシミュレートするために使用している入力方法です。私はこれを短時間実行し、すぐに500MB以上を食べる。

これは、一時ディレクトリ内に2つのダミースクリプトがある場合のみです。

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

Containerクラス


これは単純なコンテナクラスです。スクリプト


これは、.javaファイルをコンパイルし、スクリプトオブジェクトにそれらをインスタンス化する実際の方法でコンパイル

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

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ファイルに出力を格納できるように、私は、コンパイラのために作成したカスタムJavaFileManager実装です。

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

クラスファイルクラス


これは私がメモリ内のソース.javaファイルとコンパイルされた.classファイルを格納するために使用私の多目的SimpleJavaFileObject実装です。

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

は、私はまだ種類の新たなんだと私は私が遭遇した小さな問題にはいくつかの解決策を見つけるためにしばらくの間、スタックを使用している、これはそう質問を私の最初の時間である私が謝罪しますあまりにも多くの情報を含む場合、またはこれが長すぎる場合は、私はちょうど私が徹底していることを確認したかった。

+0

どのようにメモリフットプリントを測定しますか? Javaでは、プログラムが実際どれくらいのメモリを使用しているか、使用可能なメモリがどれだけあるかという大きな違いがあります。 –

+0

+1よくレイアウトされた質問のために、私はこれに対する答えを見ることに興味があります。あなたは偶然、Javaの反射を見ましたか? – FloppyDisk

+0

私は、タスクマネージャで特定のjavaw.exeプロセスのメモリ使用量が増えたことに気づいた後、Eclipse Memory Analyzerを使用していました。それはガベージコレクターが未使用の残党を集めるために何もしていないかのように思えます...また、私が睡眠を取り除いて(真実に)設定すると、それは記憶がないためにクラッシュするという事実があります。 – Jordan

答えて

5

あなたは、コンパイルされたクラスをロードするために、アプリケーションのデフォルトのクラスローダを使用しているように見える - それは不可能クラスがゴミを収集するようになり、その。

新しくコンパイルされたクラスにはcreate a separate classloaderが必要です。これは、アプリサーバーがそれを行う方法です。

コンパイルされたクラスに別のクラスローダーを使用しても、クラスローダーとそれがロードしたすべてのクラスがガベージコラボレーションに適さないため、ガベージコレクションによってそれらのクラスを取得するのは難しいことがありますそれらのクラスのいずれかのインスタンスが他の場所(つまりアプリケーションの残りの部分)に参照されている限り

これは、再デプロイが今までより多くのメモリを使用し、最終的に失敗する原因、classloader leakとappserversとに共通の問題として知られています。クラスローダーのリークを診断して修正することは非常に難しいことです。記事はすべての詳細を持っています。

+0

有益なリンク、特にクラスローダーの漏れに関する情報をありがとう。 – Jordan

関連する問題