2012-06-30 81 views
5

JavaでclassLoadersを使いこなしていて、奇妙なことに気づいた。 classLoaderがjarからクラスをロードすると、classLoaderを参照しなくてもこのjarが無期限にロックされます。ロックされたjarのJava classLoaderジレンマ

以下の例では、jarにはHelloWorldというクラスが含まれています。私がやることは、jarを動的に追加するclassLoaderを使用して、jarに含まれるクラスをロードしようとすることです。 skiptrueに設定してClass.forNameを呼び出していない場合はjarを削除できますが、スキップしない場合や、classLoaderclassLoader = null)を参照していない場合でも、JVMは終了するまで削除できません。

なぜですか?

PS:私は、java 6を使用していますが、コードはテスト目的のJava 7では

package loader; 

import java.io.File; 
import java.io.FileInputStream; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.net.MalformedURLException; 
import java.net.URL; 
import java.net.URLClassLoader; 

public class TestClassLoader { 

    private URLClassLoader classLoader; 

    public TestClassLoader() throws MalformedURLException, IOException { 
     System.out.println("Copying jar"); 
     if (copyJar()) { 
      System.out.println("Copying SUCCESS"); 
      performFirstCheck(); 
     } else { 
      System.out.println("Copying FAILED"); 
     } 
    } 

    public static void main(String[] args) throws IOException { 
     System.out.println("Test started"); 
     TestClassLoader testClassLoader = new TestClassLoader(); 
     System.out.println("Bye!"); 
    } 

    public void performFirstCheck() throws IOException { 
     System.out.println("Checking class HelloWorld does not exist"); 
     if (!checkClassFound(TestClassLoader.class.getClassLoader(), false)) { 
      System.out.println("Deleting jar"); 
      deleteJar(); 
      System.out.println("First Check SUCCESS"); 
      performSecondCheck(); 
     } else { 
      System.out.println("First Check FAILED"); 
     } 
    } 

    private void performSecondCheck() throws IOException { 
     System.out.println("Copying jar"); 
     if (copyJar()) { 
      System.out.println("Copying SUCCESS"); 
      createClassLoaderAndCheck(); 
     } else { 
      System.out.println("Copying FAILED"); 
     } 
    } 

    private void createClassLoaderAndCheck() throws MalformedURLException { 
     System.out.println("Creating classLoader"); 
     createClassLoader(); 
     System.out.println("Checking class HelloWorld exist"); 
     if (checkClassFound(classLoader, true)) { 
      System.out.println("Second Check SUCCESS"); 
        classLoader = null; 
      System.out.println("Deleting jar"); 
      if (deleteJar()) { 
       System.out.println("Deleting SUCCESS"); 
      } else { 
       System.out.println("Deleting FAILED"); 
      } 
     } else { 
      System.out.println("Second Check FAILED"); 
     } 
    } 

    public void createClassLoader() throws MalformedURLException { 
     URL[] urls = new URL[1]; 
     File classFile = new File("C:\\Users\\Adel\\Desktop\\classes.jar"); 
     urls[0] = classFile.toURI().toURL(); 
     classLoader = new URLClassLoader(urls); 
    } 

    public boolean checkClassFound(ClassLoader classLoader, boolean skip) { 
     if (skip) { 
      System.out.println("Skiping class loading"); 
      return true; 
     } else { 
      try { 
       Class.forName("HelloWorld", true, classLoader); 
       return true; 
      } catch (ClassNotFoundException e) { 
       return false; 
      } 
     } 
    } 

    public URLClassLoader getClassLoader() { 
     return classLoader; 
    } 

    public boolean copyJar() throws IOException { 
     File sourceJar = new File("C:\\Users\\Adel\\Desktop\\Folder\\classes.jar"); 
     File destJar = new File("C:\\Users\\Adel\\Desktop\\classes.jar"); 
     if (destJar.exists()) { 
      return false; 
     } else { 
      FileInputStream finput = new FileInputStream(sourceJar); 
      FileOutputStream foutput = new FileOutputStream(destJar); 
      byte[] buf = new byte[1024]; 
      int len; 
      while ((len = finput.read(buf)) > 0) { 
       foutput.write(buf, 0, len); 
      } 
      finput.close(); 
      foutput.close(); 
      return true; 
     } 
    } 

    public boolean deleteJar() { 
     File destJar = new File("C:\\Users\\Adel\\Desktop\\classes.jar"); 
     return destJar.delete(); 
    } 

} 
+0

回避策または説明をしますか? ? – esej

+1

@esej私はすでに両者を見つけました。私の答えをチェックして意見を共有してください。 –

答えて

8

私は答えと回避策を見つけました。

このarticleとこの驚くべき関連articleに基づいて、Class.forName(className, true, classLoader)を使用するのは悪い習慣です。これは、メモリにキャッシュされたクラスを無期限に保持するためです。

解決策ではなくclassLoader.loadClass(clasName)を使用していた、その後、一度classLoader unreference、終了して使用してガベージコレクタを呼び出す:

classLoader = null; 
System.gc(); 

希望、これは他の人に役立ちます! :)

背景情報:

私のプロジェクトはCOMPLEXE 1だった:私たちは別のサーバーへのRMIクライアントとして動作するGWTサーバーを持っていました。インスタンスを作成するために、GWTはクラスをサーバーからダウンロードしてロードする必要がありました。その後、GWTはHibernateを使用してデータベースに永続化するためにインスタンスをサーバに再送します。ホットデプロイメントをサポートするために、ユーザーはjarをアップロードし、クラスをロードしてGWTサーバーで利用可能なものとしてサーバーに通知する動的クラスロードを選択しました。

+0

これは、このクラスローダーによってロードされた* all *クラスと、これらのクラスの* all *インスタンスも参照されていない場合にのみ機能することに注意してください。そのようなクラスのインスタンスが1つでも存在する限り、クラスローダーは(instance.getClass()。getClassLoader()を呼び出すことができるため)メモリに保持されます。 –

+0

優れていますが、すべてのインスタンスとクラスを削除する必要があります。その場合、PermGenになる可能性があります。 Java 7では、URLClassLoader#close()を使用する必要があることに注意してください。クラスパスを読み込んでファイルをメモリに読み込んでクラスをロードすると、実際に問題を解決することはできませんでした。クラスローダーは奇妙な動物です。 – esej

+0

@esejおそらくClassLoaderのサブクラスを作成し、ファイルを手動で開き、その内容をバイト配列に読み込み、 'ClassLoader #defineClass(String name、byte [] b、int off、int len)'を呼び出すことを意味します。これは本当にうまくいくはずです。 –

2

URLClassLoaderはこれを修正する#close()方法を持っているために非常に冗長です。

+0

私はJava 6を使用していますが、これは今変更できません:D –

+0

Java 6は本当にすぐにEoLです。クラスローダーへの参照をすべて削除し、完全なGC _and_実行終了を強制的に試すことができます。しかし、これはひどいです。あるいは、独自のjarクラスローダーを作成して、closeメソッドを作成することもできます。 –

+1

私はすでにJava 6を使用して解決策を見つけました。私の答えをチェックして意見を共有してください。 –

関連する問題